KAN-73: document and validate MCP ticket description format
This commit is contained in:
@@ -5,11 +5,14 @@ namespace App\Mcp\Tools;
|
||||
use App\Actions\Permissions\PermissionService;
|
||||
use App\Actions\Tickets\CreateProjectTicket;
|
||||
use App\Actions\Tickets\Support\AcceptanceCriteriaChecklistMapper;
|
||||
use App\Actions\Tickets\Support\TicketDescriptionFormat;
|
||||
use App\Enums\Permission;
|
||||
use App\Mcp\Support\DescribesTicketWriting;
|
||||
use App\Mcp\Support\KanbanGitMcp;
|
||||
use App\Models\Project;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use App\Rules\VisualTicketDescription;
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
use Illuminate\JsonSchema\Types\Type;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -28,6 +31,8 @@ use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
|
||||
#[IsDestructive(false)]
|
||||
class CreateTicket extends Tool
|
||||
{
|
||||
use DescribesTicketWriting;
|
||||
|
||||
public function __construct(
|
||||
private readonly KanbanGitMcp $kanbanGitMcp,
|
||||
private readonly CreateProjectTicket $createProjectTicket,
|
||||
@@ -44,7 +49,7 @@ class CreateTicket extends Tool
|
||||
$project = $this->kanbanGitMcp->projectForWrite($user, (int) Arr::get($projectInput, 'project_id'));
|
||||
$validated = $request->validate([
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'description' => ['nullable', 'string', new VisualTicketDescription],
|
||||
'ticket_type_id' => ['required', 'integer', Rule::exists('ticket_types', 'id')->where('project_id', $project->id)],
|
||||
'ticket_status_id' => ['required', 'integer', Rule::exists('ticket_statuses', 'id')->where('project_id', $project->id)],
|
||||
'ticket_priority_id' => ['required', 'integer', Rule::exists('ticket_priorities', 'id')->where('project_id', $project->id)],
|
||||
@@ -140,7 +145,7 @@ class CreateTicket extends Tool
|
||||
return [
|
||||
'project_id' => $schema->integer()->description('Project ID.')->required(),
|
||||
'title' => $schema->string()->max(255)->description('Ticket title.')->required(),
|
||||
'description' => $schema->string()->nullable()->description('Ticket description.'),
|
||||
'description' => $schema->string()->nullable()->description('Ticket description. '.TicketDescriptionFormat::instructions()),
|
||||
'ticket_type_id' => $schema->integer()->description('Ticket type ID belonging to the project. Explicitly selects the type, including types that allow child tickets.')->required(),
|
||||
'ticket_status_id' => $schema->integer()->description('Initial ticket status ID belonging to the project.')->required(),
|
||||
'ticket_priority_id' => $schema->integer()->description('Ticket priority ID belonging to the project.')->required(),
|
||||
@@ -156,7 +161,7 @@ class CreateTicket extends Tool
|
||||
'checked' => $schema->boolean()->default(false),
|
||||
'indent' => $schema->integer()->min(0)->max(AcceptanceCriteriaChecklistMapper::MAX_INDENT_LEVEL)->default(0),
|
||||
])->withoutAdditionalProperties())
|
||||
->description('Ordered acceptance checklist. Indent 0 is a root item; larger values nest under preceding items.'),
|
||||
->description('Ordered acceptance checklist. Indent 0 is a root item; larger values nest under preceding items. '.TicketDescriptionFormat::criteriaInstructions()),
|
||||
'field_values' => $schema->array()
|
||||
->items($schema->object([
|
||||
'ticket_field_definition_id' => $schema->integer()->description('Custom field definition ID belonging to the project. Each definition may appear once.')->required(),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Mcp\Tools;
|
||||
|
||||
use App\Actions\Tickets\MoveProjectTicket;
|
||||
use App\Mcp\Support\DescribesTicketWriting;
|
||||
use App\Mcp\Support\KanbanGitMcp;
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
use Illuminate\JsonSchema\Types\Type;
|
||||
@@ -19,6 +20,8 @@ use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
|
||||
#[IsDestructive(false)]
|
||||
class MoveTicketStatus extends Tool
|
||||
{
|
||||
use DescribesTicketWriting;
|
||||
|
||||
public function __construct(
|
||||
private readonly KanbanGitMcp $kanbanGitMcp,
|
||||
private readonly MoveProjectTicket $moveProjectTicket,
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Mcp\Tools;
|
||||
|
||||
use App\Actions\Tickets\Support\TicketDescriptionFormat;
|
||||
use App\Actions\Tickets\UpdateProjectTicket;
|
||||
use App\Mcp\Support\DescribesTicketWriting;
|
||||
use App\Mcp\Support\KanbanGitMcp;
|
||||
use App\Mcp\Support\TicketUpdateInput;
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
@@ -20,6 +22,8 @@ use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
|
||||
#[IsDestructive(false)]
|
||||
class UpdateTicket extends Tool
|
||||
{
|
||||
use DescribesTicketWriting;
|
||||
|
||||
public function __construct(
|
||||
private readonly KanbanGitMcp $kanbanGitMcp,
|
||||
private readonly TicketUpdateInput $ticketUpdateInput,
|
||||
@@ -46,7 +50,7 @@ class UpdateTicket extends Tool
|
||||
return [
|
||||
'ticket_id' => $schema->integer()->description('Existing ticket ID.')->required(),
|
||||
'title' => $schema->string()->max(255)->description('Nonblank title; omit to preserve.'),
|
||||
'description' => $schema->string()->nullable()->description('Description; null clears it.'),
|
||||
'description' => $schema->string()->nullable()->description('Description; null clears it. '.TicketDescriptionFormat::instructions()),
|
||||
'ticket_type_id' => $schema->integer()->description('Type ID from get_project_configuration.'),
|
||||
'ticket_priority_id' => $schema->integer()->description('Priority ID from get_project_configuration.'),
|
||||
'parent_ticket_id' => $schema->integer()->nullable()->description('Parent-enabled ticket in this project; null removes the parent. Cycles are forbidden.'),
|
||||
@@ -60,7 +64,7 @@ class UpdateTicket extends Tool
|
||||
'text' => $schema->string()->description('Nonblank criterion text.')->required(),
|
||||
'checked' => $schema->boolean()->description('Checked state, defaults to false for supplied items.'),
|
||||
'indent' => $schema->integer()->min(0)->max(8)->description('Indent level, defaults to 0; cannot skip a level.'),
|
||||
])->withoutAdditionalProperties())->description('Ordered replacement checklist. Omitted items are removed. [] clears; omit field to preserve; null is invalid.'),
|
||||
])->withoutAdditionalProperties())->description('Ordered replacement checklist. Omitted items are removed. [] clears; omit field to preserve; null is invalid. '.TicketDescriptionFormat::criteriaInstructions()),
|
||||
'field_values' => $schema->array()->items($schema->object([
|
||||
'ticket_field_definition_id' => $schema->integer()->description('Project field ID, supplied once.')->required(),
|
||||
'value_number' => $schema->number()->nullable()->description('Value for a numeric field; null clears.'),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Mcp\Tools;
|
||||
|
||||
use App\Actions\Tickets\UpdateProjectTicket;
|
||||
use App\Mcp\Support\DescribesTicketWriting;
|
||||
use App\Mcp\Support\KanbanGitMcp;
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
use Illuminate\JsonSchema\Types\Type;
|
||||
@@ -19,6 +20,8 @@ use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
|
||||
#[IsDestructive(false)]
|
||||
class UpdateTicketAssignee extends Tool
|
||||
{
|
||||
use DescribesTicketWriting;
|
||||
|
||||
public function __construct(
|
||||
private readonly KanbanGitMcp $kanbanGitMcp,
|
||||
private readonly UpdateProjectTicket $updateProjectTicket,
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
namespace App\Mcp\Tools;
|
||||
|
||||
use App\Actions\Tickets\Support\TicketDescriptionFormat;
|
||||
use App\Actions\Tickets\UpdateProjectTicket;
|
||||
use App\Mcp\Support\DescribesTicketWriting;
|
||||
use App\Mcp\Support\KanbanGitMcp;
|
||||
use App\Rules\VisualTicketDescription;
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
use Illuminate\JsonSchema\Types\Type;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\ResponseFactory;
|
||||
@@ -19,6 +23,8 @@ use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
|
||||
#[IsDestructive(false)]
|
||||
class UpdateTicketDescription extends Tool
|
||||
{
|
||||
use DescribesTicketWriting;
|
||||
|
||||
public function __construct(
|
||||
private readonly KanbanGitMcp $kanbanGitMcp,
|
||||
private readonly UpdateProjectTicket $updateProjectTicket,
|
||||
@@ -27,7 +33,7 @@ class UpdateTicketDescription extends Tool
|
||||
public function handle(Request $request): Response|ResponseFactory
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'description' => ['nullable', 'string'],
|
||||
'description' => ['nullable', 'string', new VisualTicketDescription],
|
||||
'ticket_id' => ['required', 'integer'],
|
||||
]);
|
||||
|
||||
@@ -35,7 +41,7 @@ class UpdateTicketDescription extends Tool
|
||||
$ticket = $this->kanbanGitMcp->ticketForWrite($user, (int) $validated['ticket_id']);
|
||||
$payload = [
|
||||
...$this->kanbanGitMcp->updatePayload($ticket),
|
||||
'description' => $validated['description'] ?? null,
|
||||
...(Arr::exists($validated, 'description') ? ['description' => Arr::get($validated, 'description')] : []),
|
||||
];
|
||||
|
||||
$wasUpdated = $this->updateProjectTicket->handle($ticket, $user, $payload);
|
||||
@@ -58,7 +64,7 @@ class UpdateTicketDescription extends Tool
|
||||
return [
|
||||
'description' => $schema->string()
|
||||
->nullable()
|
||||
->description('New ticket description. Pass null to clear it.'),
|
||||
->description('New ticket description. Omit to preserve; pass null to clear it. '.TicketDescriptionFormat::instructions()),
|
||||
'ticket_id' => $schema->integer()
|
||||
->description('Ticket ID.')
|
||||
->required(),
|
||||
|
||||
Reference in New Issue
Block a user