McpConfigCommand.php•7.48 kB
<?php
declare(strict_types=1);
namespace Butschster\ContextGenerator\McpServer\Console;
use Butschster\ContextGenerator\Console\BaseCommand;
use Butschster\ContextGenerator\DirectoriesInterface;
use Butschster\ContextGenerator\McpServer\McpConfig\Client\ClientStrategyRegistry;
use Butschster\ContextGenerator\McpServer\McpConfig\ConfigGeneratorInterface;
use Butschster\ContextGenerator\McpServer\McpConfig\Service\OsDetectionService;
use Spiral\Console\Attribute\Option;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
#[AsCommand(
    name: 'mcp:config',
    description: 'Generate MCP configuration for connecting CTX to Claude or other MCP clients',
)]
final class McpConfigCommand extends BaseCommand
{
    #[Option(
        name: 'wsl',
        shortcut: 'f',
        description: 'Force WSL configuration mode',
    )]
    protected bool $forceWsl = false;
    #[Option(
        name: 'explain',
        shortcut: 'e',
        description: 'Show detailed setup instructions',
    )]
    protected bool $explain = false;
    #[Option(
        name: 'client',
        shortcut: 'c',
        description: 'MCP client type (claude, codex, cursor, generic)',
    )]
    protected ?string $client = null;
    #[Option(
        name: 'project-path',
        shortcut: 'p',
        description: 'Use specific project path in configuration',
    )]
    protected ?string $projectPath = null;
    #[Option(
        name: 'global',
        shortcut: 'g',
        description: 'Use global project registry (no -c option)',
    )]
    protected bool $useGlobal = true;
    #[Option(
        name: 'sse',
        description: 'Enable SSE (Server-Sent Events) transport mode',
    )]
    protected bool $useSse = false;
    #[Option(
        name: 'host',
        description: 'SSE host to bind to (default: 127.0.0.1)',
    )]
    protected string $sseHost = '127.0.0.1';
    #[Option(
        name: 'port',
        description: 'SSE port to bind to (default: 8080)',
    )]
    protected int $ssePort = 8080;
    public function __invoke(
        OsDetectionService $osDetection,
        ConfigGeneratorInterface $configGenerator,
        DirectoriesInterface $dirs,
        ClientStrategyRegistry $registry,
    ): int {
        $this->output->title('MCP Configuration Generator');
        // Handle interactive mode
        if ($this->client === null) {
            return $this->runInteractiveMode($osDetection, $configGenerator, $registry, $dirs);
        }
        // Detect operating system
        $osInfo = $osDetection->detect($this->forceWsl);
        // Build configuration options
        $options = $this->buildConfigOptions($dirs);
        // Get client strategy
        $strategy = $registry->getByKey($this->client) ?? $registry->getDefault();
        // Generate configuration
        $config = $configGenerator->generate(
            client: $strategy->getGeneratorClientKey(),
            osInfo: $osInfo,
            projectPath: $options['project_path'] ?? (string) $dirs->getRootPath(),
            options: $options,
        );
        // Render using strategy
        $strategy->renderConfiguration($config, $osInfo, $options, $this->output);
        $strategy->renderInstructions($config, $osInfo, $options, $this->output);
        return Command::SUCCESS;
    }
    private function runInteractiveMode(
        OsDetectionService $osDetection,
        ConfigGeneratorInterface $configGenerator,
        ClientStrategyRegistry $registry,
        DirectoriesInterface $dirs,
    ): int {
        $this->output->section('Interactive Configuration');
        $this->output->text('Let\'s configure your MCP client step by step...');
        $this->output->newLine();
        // Ask about client type
        $choice = $this->output->choice(
            'Which MCP client are you configuring?',
            $registry->getChoiceLabels(),
            $registry->getDefault()->getLabel(),
        );
        $strategy = $registry->getByLabel($choice)
            ?? $registry->getByKey(\strtolower(\trim((string) $choice)))
            ?? $registry->getDefault();
        // Detect OS
        $osInfo = $osDetection->detect();
        $this->output->section('Environment');
        $this->output->definitionList(
            ['Operating System' => $osInfo->getDisplayName()],
            ['Architecture' => $osInfo->additionalInfo['architecture'] ?? 'Unknown'],
        );
        $this->output->newLine();
        // Ask about WSL if on Windows
        if ($osInfo->isWindows() && !$osInfo->isWsl()) {
            $useWsl = $this->output->confirm(
                'Are you using WSL (Windows Subsystem for Linux)?',
                false,
            );
            if ($useWsl) {
                $osInfo = $osDetection->detect(forceWsl: true);
            }
        }
        // Ask about project configuration
        $configChoice = $this->output->choice(
            'How do you want to configure project access?',
            [
                'global' => 'Global project registry (switch projects dynamically)',
                'specific' => 'Specific project path (single project)',
            ],
            'global',
        );
        $options = ['use_project_path' => false];
        $projectPath = (string) $dirs->getRootPath();
        if ($configChoice === 'specific') {
            $options['use_project_path'] = true;
            $projectPath = $this->output->ask(
                'Project path:',
                (string) $dirs->getRootPath(),
            );
            if (!\is_dir($projectPath)) {
                $this->output->warning("Path does not exist: {$projectPath}");
                if (!$this->output->confirm('Continue anyway?', true)) {
                    return Command::FAILURE;
                }
            }
        }
        // Ask about SSE mode
        $useSse = $this->output->confirm(
            'Enable SSE (Server-Sent Events) transport mode for remote access?',
            false,
        );
        if ($useSse) {
            $options['use_sse'] = true;
            $options['sse_host'] = $this->output->ask('SSE host:', '127.0.0.1');
            $options['sse_port'] = (int) $this->output->ask('SSE port:', '8080');
        }
        // Generate and display configuration
        $config = $configGenerator->generate(
            client: $strategy->getGeneratorClientKey(),
            osInfo: $osInfo,
            projectPath: $projectPath,
            options: $options,
        );
        $strategy->renderConfiguration($config, $osInfo, $options, $this->output);
        $strategy->renderInstructions($config, $osInfo, $options, $this->output);
        return Command::SUCCESS;
    }
    private function buildConfigOptions(DirectoriesInterface $dirs): array
    {
        $options = [];
        if ($this->projectPath !== null) {
            $options['use_project_path'] = true;
            $options['project_path'] = $this->projectPath;
        } elseif (!$this->useGlobal) {
            $options['use_project_path'] = true;
            $options['project_path'] = (string) $dirs->getRootPath();
        } else {
            $options['use_project_path'] = false;
        }
        // Add SSE options if enabled
        if ($this->useSse) {
            $options['use_sse'] = true;
            $options['sse_host'] = $this->sseHost;
            $options['sse_port'] = $this->ssePort;
        }
        return $options;
    }
}