Tools: list-users, list-clients, list/get/create/update-project, list/get/create/update/delete-task, list-statuses/priorities/efforts/tags, list/create/update-group, list/create/update/delete-time-entry. Attribute moved to class level for SDK discovery compatibility. Install nyholm/psr7 for HTTP transport PSR-17 support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
89 lines
3.3 KiB
PHP
89 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Mcp\Tool\TimeEntry;
|
|
|
|
use App\Repository\TimeEntryRepository;
|
|
use DateTimeImmutable;
|
|
use Mcp\Capability\Attribute\McpTool;
|
|
|
|
#[McpTool(name: 'list-time-entries', description: 'List time entries with optional filters. Duration is computed in minutes and null for active timers.')]
|
|
class ListTimeEntriesTool
|
|
{
|
|
public function __construct(
|
|
private readonly TimeEntryRepository $timeEntryRepository,
|
|
) {}
|
|
|
|
public function __invoke(
|
|
?int $userId = null,
|
|
?int $projectId = null,
|
|
?int $taskId = null,
|
|
?string $startDate = null,
|
|
?string $endDate = null,
|
|
int $limit = 100,
|
|
): string {
|
|
$limit = min($limit, 200);
|
|
|
|
$qb = $this->timeEntryRepository->createQueryBuilder('te')
|
|
->leftJoin('te.user', 'u')->addSelect('u')
|
|
->leftJoin('te.project', 'p')->addSelect('p')
|
|
->leftJoin('te.task', 't')->addSelect('t')
|
|
->leftJoin('te.tags', 'tg')->addSelect('tg')
|
|
->orderBy('te.startedAt', 'DESC')
|
|
->setMaxResults($limit)
|
|
;
|
|
|
|
if (null !== $userId) {
|
|
$qb->andWhere('u.id = :userId')->setParameter('userId', $userId);
|
|
}
|
|
if (null !== $projectId) {
|
|
$qb->andWhere('p.id = :projectId')->setParameter('projectId', $projectId);
|
|
}
|
|
if (null !== $taskId) {
|
|
$qb->andWhere('t.id = :taskId')->setParameter('taskId', $taskId);
|
|
}
|
|
if (null !== $startDate) {
|
|
$qb->andWhere('te.startedAt >= :startDate')
|
|
->setParameter('startDate', new DateTimeImmutable($startDate.' 00:00:00'))
|
|
;
|
|
}
|
|
if (null !== $endDate) {
|
|
$qb->andWhere('te.startedAt <= :endDate')
|
|
->setParameter('endDate', new DateTimeImmutable($endDate.' 23:59:59'))
|
|
;
|
|
}
|
|
|
|
$entries = $qb->getQuery()->getResult();
|
|
|
|
return json_encode(array_map(fn ($entry) => [
|
|
'id' => $entry->getId(),
|
|
'title' => $entry->getTitle(),
|
|
'description' => $entry->getDescription(),
|
|
'startedAt' => $entry->getStartedAt()?->format('c'),
|
|
'stoppedAt' => $entry->getStoppedAt()?->format('c'),
|
|
'duration' => $entry->getStoppedAt() && $entry->getStartedAt()
|
|
? (int) round(($entry->getStoppedAt()->getTimestamp() - $entry->getStartedAt()->getTimestamp()) / 60)
|
|
: null,
|
|
'user' => [
|
|
'id' => $entry->getUser()->getId(),
|
|
'username' => $entry->getUser()->getUsername(),
|
|
],
|
|
'project' => $entry->getProject() ? [
|
|
'id' => $entry->getProject()->getId(),
|
|
'code' => $entry->getProject()->getCode(),
|
|
'name' => $entry->getProject()->getName(),
|
|
] : null,
|
|
'task' => $entry->getTask() ? [
|
|
'id' => $entry->getTask()->getId(),
|
|
'number' => $entry->getTask()->getNumber(),
|
|
'title' => $entry->getTask()->getTitle(),
|
|
] : null,
|
|
'tags' => $entry->getTags()->map(fn ($t) => [
|
|
'id' => $t->getId(),
|
|
'label' => $t->getLabel(),
|
|
])->toArray(),
|
|
], $entries));
|
|
}
|
|
}
|