Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions backend/src/Taskdeck.Api/Mcp/WriteTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Taskdeck.Application.Interfaces;
using Taskdeck.Application.Services;
using Taskdeck.Application.Services.Pipeline;
using Taskdeck.Domain.Common;
using Taskdeck.Domain.Entities;

namespace Taskdeck.Api.Mcp;
Expand Down Expand Up @@ -102,7 +103,7 @@ public async Task<string> CreateCard(

var canWrite = await _authorizationService.CanWriteBoardAsync(userId, boardGuid);
if (!canWrite.IsSuccess)
return Error(canWrite.ErrorMessage);
return Error(canWrite);
if (!canWrite.Value)
return Error("Not authorized to create cards on this board");

Expand Down Expand Up @@ -171,7 +172,7 @@ public async Task<string> CreateCard(

var result = await _proposalService.CreateProposalAsync(dto);
if (!result.IsSuccess)
return Error(result.ErrorMessage);
return Error(result);

return ProposalCreated(result.Value.Id, "Proposal created. Review and approve in Taskdeck to create the card.");
}
Expand Down Expand Up @@ -230,7 +231,7 @@ public async Task<string> MoveCard(

var result = await _proposalService.CreateProposalAsync(dto);
if (!result.IsSuccess)
return Error(result.ErrorMessage);
return Error(result);

return ProposalCreated(result.Value.Id, "Proposal created. Review and approve in Taskdeck to move the card.");
}
Expand Down Expand Up @@ -322,7 +323,7 @@ public async Task<string> UpdateCard(

var result = await _proposalService.CreateProposalAsync(dto);
if (!result.IsSuccess)
return Error(result.ErrorMessage);
return Error(result);

return ProposalCreated(result.Value.Id, "Proposal created. Review and approve in Taskdeck to update the card.");
}
Expand Down Expand Up @@ -375,7 +376,7 @@ public async Task<string> ArchiveCard(

var result = await _proposalService.CreateProposalAsync(dto);
if (!result.IsSuccess)
return Error(result.ErrorMessage);
return Error(result);

return ProposalCreated(
result.Value.Id,
Expand Down Expand Up @@ -414,7 +415,7 @@ public async Task<string> CreateCapture(

var result = await _captureService.CreateAsync(userId, captureDto);
if (!result.IsSuccess)
return Error(result.ErrorMessage);
return Error(result);

return JsonSerializer.Serialize(new
{
Expand Down Expand Up @@ -447,14 +448,14 @@ public async Task<string> CreateColumn(

var canWrite = await _authorizationService.CanWriteBoardAsync(userId, boardGuid);
if (!canWrite.IsSuccess)
return Error(canWrite.ErrorMessage);
return Error(canWrite);
if (!canWrite.Value)
return Error("Not authorized to create columns on this board");

var columns = (await _unitOfWork.Columns.GetByBoardIdAsync(boardGuid)).ToList();
var appendPositionResult = ProposalOperationContractValidator.ResolveAppendPosition(columns);
if (!appendPositionResult.IsSuccess)
return Error(appendPositionResult.ErrorMessage);
return Error(appendPositionResult);

var parameters = new Dictionary<string, object?>
{
Expand Down Expand Up @@ -487,7 +488,7 @@ public async Task<string> CreateColumn(
boardGuid,
new[] { operation });
if (!contractValidation.IsSuccess)
return Error(contractValidation.ErrorMessage);
return Error(contractValidation);

var dto = new CreateProposalDto(
SourceType: ProposalSourceType.Manual,
Expand All @@ -503,7 +504,7 @@ public async Task<string> CreateColumn(

var result = await _proposalService.CreateProposalAsync(dto);
if (!result.IsSuccess)
return Error(result.ErrorMessage);
return Error(result);

return ProposalCreated(result.Value.Id, "Proposal created. Review and approve in Taskdeck to create the column.");
}
Expand Down Expand Up @@ -548,6 +549,18 @@ private static string Error(string message)
return JsonSerializer.Serialize(new { error = message }, BoardResources.SerializerOptions);
}

/// <summary>
/// Serializes a failed application <see cref="Result"/> for an MCP caller. A result
/// classified <c>UnexpectedError</c> collapses to the stable generic failure message so
/// unknown-exception text never reaches the model; known domain messages stay specific.
/// </summary>
private static string Error(Result result)
{
return Error(SensitiveDataRedactor.SanitizeLlmFailureMessage(
result.ErrorCode,
result.ErrorMessage));
}

private static string ProposalCreated(Guid proposalId, string message)
{
return JsonSerializer.Serialize(new
Expand Down
Loading
Loading