fix : apply review fixes to MCP plan and spec

Fix getIsFinal() method name, enrich create/update tool return formats
to match get/list consistency, fix duplicate Reference section in spec,
correct tool count to 22.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 19:27:06 +01:00
parent f27297517c
commit 9a9416d6c8

View File

@@ -4,7 +4,7 @@
**Goal:** Add an MCP server to Lesstime exposing projects, tasks, and time tracking for AI clients via STDIO and HTTP transports.
**Architecture:** Install `symfony/mcp-bundle`, create 21 tool classes in `src/Mcp/Tool/` organized by domain (Project, Task, TaskMeta, TimeEntry, Reference). HTTP transport secured by API token on User entity with a custom Symfony authenticator. STDIO for local Claude Code usage.
**Architecture:** Install `symfony/mcp-bundle`, create 22 tool classes in `src/Mcp/Tool/` organized by domain (Project, Task, TaskMeta, TimeEntry, Reference). HTTP transport secured by API token on User entity with a custom Symfony authenticator. STDIO for local Claude Code usage.
**Tech Stack:** symfony/mcp-bundle, Symfony 8 security (custom authenticator), Doctrine ORM, PHP 8.4
@@ -931,7 +931,7 @@ class GetTaskTool
'id' => $task->getStatus()->getId(),
'label' => $task->getStatus()->getLabel(),
'color' => $task->getStatus()->getColor(),
'isFinal' => $task->getStatus()->isFinal(),
'isFinal' => $task->getStatus()->getIsFinal(),
] : null,
'priority' => $task->getPriority() ? [
'id' => $task->getPriority()->getId(),
@@ -1109,16 +1109,39 @@ class CreateTaskTool
'id' => $task->getId(),
'number' => $task->getNumber(),
'title' => $task->getTitle(),
'description' => $task->getDescription(),
'status' => $task->getStatus() ? [
'id' => $task->getStatus()->getId(),
'label' => $task->getStatus()->getLabel(),
'color' => $task->getStatus()->getColor(),
] : null,
'priority' => $task->getPriority() ? [
'id' => $task->getPriority()->getId(),
'label' => $task->getPriority()->getLabel(),
'color' => $task->getPriority()->getColor(),
] : null,
'effort' => $task->getEffort() ? [
'id' => $task->getEffort()->getId(),
'label' => $task->getEffort()->getLabel(),
] : null,
'assignee' => $task->getAssignee() ? [
'id' => $task->getAssignee()->getId(),
'username' => $task->getAssignee()->getUsername(),
] : null,
'group' => $task->getGroup() ? [
'id' => $task->getGroup()->getId(),
'title' => $task->getGroup()->getTitle(),
] : null,
'project' => [
'id' => $project->getId(),
'code' => $project->getCode(),
'name' => $project->getName(),
],
'status' => $task->getStatus() ? ['id' => $task->getStatus()->getId(), 'label' => $task->getStatus()->getLabel()] : null,
'priority' => $task->getPriority() ? ['id' => $task->getPriority()->getId(), 'label' => $task->getPriority()->getLabel()] : null,
'effort' => $task->getEffort() ? ['id' => $task->getEffort()->getId(), 'label' => $task->getEffort()->getLabel()] : null,
'assignee' => $task->getAssignee() ? ['id' => $task->getAssignee()->getId(), 'username' => $task->getAssignee()->getUsername()] : null,
'group' => $task->getGroup() ? ['id' => $task->getGroup()->getId(), 'title' => $task->getGroup()->getTitle()] : null,
'tags' => $task->getTags()->map(fn($t) => [
'id' => $t->getId(),
'label' => $t->getLabel(),
])->toArray(),
'archived' => $task->isArchived(),
]);
}
}
@@ -1242,13 +1265,38 @@ class UpdateTaskTool
'id' => $task->getId(),
'number' => $task->getNumber(),
'title' => $task->getTitle(),
'description' => $task->getDescription(),
'status' => $task->getStatus() ? [
'id' => $task->getStatus()->getId(),
'label' => $task->getStatus()->getLabel(),
'color' => $task->getStatus()->getColor(),
] : null,
'priority' => $task->getPriority() ? [
'id' => $task->getPriority()->getId(),
'label' => $task->getPriority()->getLabel(),
'color' => $task->getPriority()->getColor(),
] : null,
'effort' => $task->getEffort() ? [
'id' => $task->getEffort()->getId(),
'label' => $task->getEffort()->getLabel(),
] : null,
'assignee' => $task->getAssignee() ? [
'id' => $task->getAssignee()->getId(),
'username' => $task->getAssignee()->getUsername(),
] : null,
'group' => $task->getGroup() ? [
'id' => $task->getGroup()->getId(),
'title' => $task->getGroup()->getTitle(),
] : null,
'project' => [
'id' => $task->getProject()->getId(),
'code' => $task->getProject()->getCode(),
'name' => $task->getProject()->getName(),
],
'status' => $task->getStatus() ? ['id' => $task->getStatus()->getId(), 'label' => $task->getStatus()->getLabel()] : null,
'priority' => $task->getPriority() ? ['id' => $task->getPriority()->getId(), 'label' => $task->getPriority()->getLabel()] : null,
'assignee' => $task->getAssignee() ? ['id' => $task->getAssignee()->getId(), 'username' => $task->getAssignee()->getUsername()] : null,
'tags' => $task->getTags()->map(fn($t) => [
'id' => $t->getId(),
'label' => $t->getLabel(),
])->toArray(),
'archived' => $task->isArchived(),
]);
}
@@ -1352,7 +1400,7 @@ class ListStatusesTool
'label' => $s->getLabel(),
'color' => $s->getColor(),
'position' => $s->getPosition(),
'isFinal' => $s->isFinal(),
'isFinal' => $s->getIsFinal(),
], $statuses));
}
}
@@ -1851,11 +1899,27 @@ class CreateTimeEntryTool
return json_encode([
'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' => $user->getId(), 'username' => $user->getUsername()],
'project' => $entry->getProject() ? ['id' => $entry->getProject()->getId(), 'code' => $entry->getProject()->getCode()] : null,
'task' => $entry->getTask() ? ['id' => $entry->getTask()->getId(), 'number' => $entry->getTask()->getNumber()] : null,
'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(),
]);
}
}
@@ -1957,8 +2021,20 @@ class UpdateTimeEntryTool
? (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()] : null,
'task' => $entry->getTask() ? ['id' => $entry->getTask()->getId(), 'number' => $entry->getTask()->getNumber()] : null,
'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(),
]);
}
}