ProjectSwitchToolAction.php•5.45 kB
<?php
declare(strict_types=1);
namespace Butschster\ContextGenerator\McpServer\Projects\Actions;
use Butschster\ContextGenerator\Application\FSPath;
use Butschster\ContextGenerator\McpServer\Attribute\InputSchema;
use Butschster\ContextGenerator\McpServer\Attribute\Tool;
use Butschster\ContextGenerator\McpServer\Projects\Actions\Dto\AliasResolutionResponse;
use Butschster\ContextGenerator\McpServer\Projects\Actions\Dto\CurrentProjectResponse;
use Butschster\ContextGenerator\McpServer\Projects\Actions\Dto\ProjectSwitchRequest;
use Butschster\ContextGenerator\McpServer\Projects\Actions\Dto\ProjectSwitchResponse;
use Butschster\ContextGenerator\McpServer\Projects\ProjectServiceInterface;
use Butschster\ContextGenerator\McpServer\Action\ToolResult;
use Butschster\ContextGenerator\McpServer\Routing\Attribute\Post;
use PhpMcp\Schema\Result\CallToolResult;
use Psr\Log\LoggerInterface;
#[Tool(
name: 'project-switch',
description: 'Switch to a different project by path or alias',
title: 'Project Switch',
)]
#[InputSchema(class: ProjectSwitchRequest::class)]
final readonly class ProjectSwitchToolAction
{
public function __construct(
private LoggerInterface $logger,
private ProjectServiceInterface $projectService,
) {}
#[Post(path: '/tools/call/project-switch', name: 'tools.project-switch')]
public function __invoke(ProjectSwitchRequest $request): CallToolResult
{
$this->logger->info('Processing project-switch tool', [
'pathOrAlias' => $request->alias,
]);
try {
$pathOrAlias = $request->alias;
if (empty($pathOrAlias)) {
return ToolResult::error('Missing pathOrAlias parameter');
}
// Handle using an alias as the path
$resolvedPath = $this->projectService->resolvePathOrAlias($pathOrAlias);
$wasAlias = $resolvedPath !== $pathOrAlias;
// Normalize path to absolute path
$projectPath = $this->normalizePath($resolvedPath);
// Check if the project exists in our registry
$projects = $this->projectService->getProjects();
if (!isset($projects[$projectPath])) {
$availableProjects = \array_keys($projects);
$availableAliases = \array_keys($this->projectService->getAliases());
$suggestions = [];
if (!empty($availableProjects)) {
$suggestions[] = 'Available project paths: ' . \implode(', ', $availableProjects);
}
if (!empty($availableAliases)) {
$suggestions[] = 'Available aliases: ' . \implode(', ', $availableAliases);
}
return ToolResult::error(
\sprintf(
"Project '%s' is not registered.\n%s",
$projectPath,
\implode("\n", $suggestions),
),
);
}
// Try to switch to this project
if ($this->projectService->switchToProject($projectPath)) {
$currentProject = $this->projectService->getCurrentProject();
$aliases = $this->projectService->getAliasesForPath($projectPath);
$currentProjectResponse = new CurrentProjectResponse(
path: $currentProject->path,
configFile: $currentProject->hasConfigFile() ? $currentProject->getConfigFile() : null,
envFile: $currentProject->hasEnvFile() ? $currentProject->getEnvFile() : null,
aliases: $aliases,
);
$aliasResolution = null;
if ($wasAlias) {
$aliasResolution = new AliasResolutionResponse(
originalAlias: $pathOrAlias,
resolvedPath: $projectPath,
);
}
$response = new ProjectSwitchResponse(
success: true,
message: \sprintf('Successfully switched to project: %s', $projectPath),
currentProject: $currentProjectResponse,
resolvedFromAlias: $aliasResolution,
);
return ToolResult::success($response);
}
$response = new ProjectSwitchResponse(
success: false,
message: \sprintf("Failed to switch to project '%s'", $projectPath),
);
return ToolResult::error($response->message);
} catch (\Throwable $e) {
$this->logger->error('Error switching project', [
'pathOrAlias' => $request->alias,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return ToolResult::error($e->getMessage());
}
}
/**
* Normalize a path to an absolute path
*/
private function normalizePath(string $path): string
{
// Handle special case for current directory
if ($path === '.') {
return (string) FSPath::cwd();
}
$pathObj = FSPath::create($path);
// If path is relative, make it absolute from the current directory
if ($pathObj->isRelative()) {
$pathObj = $pathObj->absolute();
}
return $pathObj->toString();
}
}