diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 8debc656a1452378003f89d6408ce2de30033a00..d5d99d29ad9db4235672d47cb5d1e8ad00171611 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -404,6 +404,12 @@ func newRecipeSaveResult(recipeID string) (*sdk.CallToolResult, SaveRecipeOutput return newToolResult(formatRecipeSave(output), output), output, nil } +func newRecipeOverwriteResult(recipeID string) (*sdk.CallToolResult, SaveRecipeOutput, error) { + output := SaveRecipeOutput{RecipeID: recipeID} + + return newToolResult(formatRecipeOverwrite(output), output), output, nil +} + func (s *Server) callDeleteRecipeTool( ctx context.Context, _ *sdk.CallToolRequest, @@ -1039,6 +1045,10 @@ func formatRecipeSave(output SaveRecipeOutput) string { return "Saved recipe (id: " + output.RecipeID + ")." } +func formatRecipeOverwrite(output SaveRecipeOutput) string { + return "Saved draft into existing recipe (id: " + output.RecipeID + ")." +} + func formatRecipeURLImportDraft(output SaveRecipeOutput) string { title := output.Title if strings.TrimSpace(title) == "" { @@ -1124,7 +1134,7 @@ type ExtractIntoRecipeOutput struct { // SaveRecipeArguments contains save_recipe tool arguments. type SaveRecipeArguments struct { Source string `json:"source" jsonschema:"Recipe save source: raw_text, prepared, url, draft, or existing."` - RecipeID string `json:"recipe_id,omitempty" jsonschema:"Recipe ID for existing recipe updates."` + RecipeID string `json:"recipe_id,omitempty" jsonschema:"Recipe ID for source=existing updates or source=draft overwrite into an existing recipe."` DraftID string `json:"draft_id,omitempty" jsonschema:"Draft ID for reviewed URL import draft saves."` Title string `json:"title,omitempty" jsonschema:"Recipe title for raw_text or prepared saves."` Text string `json:"text,omitempty" jsonschema:"Raw recipe text for raw_text saves."` diff --git a/internal/mcp/server_save_draft.go b/internal/mcp/server_save_draft.go index a2b49f53d4a41639910608d66be41f3cc8f41d0d..84f583b41b5bcd0f3b37a78303acaaf5da4e825a 100644 --- a/internal/mcp/server_save_draft.go +++ b/internal/mcp/server_save_draft.go @@ -39,13 +39,22 @@ func (s *Server) callDraftSaveRecipeTool( portions = content.Portions } - recipeID, err := s.backend.SaveRecipeDraft(ctx, draftID, markdown, portions) + recipeID := strings.TrimSpace(arguments.RecipeID) + if recipeID != "" { + if err := s.backend.UpdateRecipeContent(ctx, recipeID, markdown, portions); err != nil { + return nil, SaveRecipeOutput{}, err + } + + return newRecipeOverwriteResult(recipeID) + } + + savedRecipeID, err := s.backend.SaveRecipeDraft(ctx, draftID, markdown, portions) if err != nil { return nil, SaveRecipeOutput{}, err } - if strings.TrimSpace(recipeID) == "" { + if strings.TrimSpace(savedRecipeID) == "" { return nil, SaveRecipeOutput{}, fmt.Errorf("save recipe draft response missing recipe ID") } - return newRecipeSaveResult(recipeID) + return newRecipeSaveResult(savedRecipeID) } diff --git a/internal/mcp/server_save_draft_test.go b/internal/mcp/server_save_draft_test.go index 4e31e02759268eaa7593baded53de6ca7a5ae137..00a8df4f942c6382728da42a9b1a38373317f1b5 100644 --- a/internal/mcp/server_save_draft_test.go +++ b/internal/mcp/server_save_draft_test.go @@ -58,6 +58,46 @@ func TestCallToolACIDRecipesSave7And9And16SavesReviewedDraft(t *testing.T) { } } +// recipes.SAVE.7-5 recipes.SAVE.9-1 recipes.SAVE.16 recipes.SAFETY.1 recipes.SAFETY.2 +func TestCallToolACIDRecipesSave7_5And9_1OverwritesExistingRecipeFromDraft(t *testing.T) { + backend := &fakeBackend{} + server := NewServer(backend, "test") + + _, output, err := server.callSaveRecipeTool( + context.Background(), + nil, + SaveRecipeArguments{ + Source: " draft ", + DraftID: " draft-1 ", + RecipeID: " recipe-1 ", + Markdown: " # Pasta\n\n1. Boil pasta.\n", + Portions: 2, + }, + ) + if err != nil { + t.Fatalf("callSaveRecipeTool() error = %v", err) + } + + if backend.updateCalls != 1 { + t.Fatalf("update calls = %d, want 1", backend.updateCalls) + } + if backend.updateRecipeID != "recipe-1" { + t.Fatalf("backend update recipe ID = %q, want recipe-1", backend.updateRecipeID) + } + if backend.updateMarkdown != " # Pasta\n\n1. Boil pasta.\n" { + t.Fatalf("backend update markdown = %q, want raw markdown preserved", backend.updateMarkdown) + } + if backend.updatePortions != 2 { + t.Fatalf("backend update portions = %g, want 2", backend.updatePortions) + } + if output.RecipeID != "recipe-1" { + t.Fatalf("save output recipe ID = %q, want recipe-1", output.RecipeID) + } + if backend.draftSaveCalls != 0 || backend.saveCalls != 0 { + t.Fatalf("draft save/prepared calls = %d/%d, want none", backend.draftSaveCalls, backend.saveCalls) + } +} + // recipes.SAVE.7 recipes.SAVE.7-3 recipes.SAVE.9 recipes.SAVE.16 tools.SAVE_RECIPE_TOOL.5 func TestCallToolACIDRecipesSave7_3SavesCurrentDraftContentWhenContentOmitted(t *testing.T) { backend := &fakeBackend{