From 9a9416d6c8dfd8ec32b3c4cb4c342f1d2ee5bee9 Mon Sep 17 00:00:00 2001 From: matthieu Date: Sun, 15 Mar 2026 19:27:06 +0100 Subject: [PATCH] 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) --- .../plans/2026-03-15-mcp-server.md | 106 +++++++++++++++--- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/docs/superpowers/plans/2026-03-15-mcp-server.md b/docs/superpowers/plans/2026-03-15-mcp-server.md index 2c65edd..050be76 100644 --- a/docs/superpowers/plans/2026-03-15-mcp-server.md +++ b/docs/superpowers/plans/2026-03-15-mcp-server.md @@ -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(), ]); } }