@@ -95,7 +95,10 @@ func (s *Server) callReadTool(
case "recipes":
return s.callRecipesReadTool(ctx, arguments)
default:
- return nil, ReadOutput{}, fmt.Errorf("unsupported read target %q in this slice", arguments.Target)
+ return nil, ReadOutput{}, fmt.Errorf(
+ "unsupported read target %q (supported: shopping_list, recipes, recipe)",
+ arguments.Target,
+ )
}
}
@@ -235,7 +238,10 @@ func (s *Server) callSaveRecipeTool(
case "":
return nil, SaveRecipeOutput{}, fmt.Errorf("source is required")
default:
- return nil, SaveRecipeOutput{}, fmt.Errorf("unsupported save_recipe source %q in this slice", source)
+ return nil, SaveRecipeOutput{}, fmt.Errorf(
+ "unsupported save_recipe source %q (supported: raw_text, prepared, url, draft, existing)",
+ source,
+ )
}
}
@@ -448,7 +454,7 @@ func (s *Server) callChangeShoppingListTool(
return nil, ChangeShoppingListOutput{}, fmt.Errorf("action is required")
default:
return nil, ChangeShoppingListOutput{}, fmt.Errorf(
- "unsupported change_shopping_list action %q in this slice",
+ "unsupported change_shopping_list action %q (supported: add, update_item, replace_selection, add_selection, remove_selection, remove, clear)",
action,
)
}
@@ -682,7 +688,7 @@ func saveRecipeTool() *sdk.Tool {
return &sdk.Tool{
Name: saveRecipeToolName,
Title: "Save recipe",
- Description: "Save a recipe. This slice supports source raw_text with title and text, source prepared with title, markdown, and portions, source url with url, source draft with draft_id, markdown, and portions, and source existing with recipe_id, markdown, and portions.",
+ Description: "Save a recipe from raw text, prepared markdown, a URL import, a reviewed URL-import draft, or an existing recipe update. URL imports may return a draft_id for review instead of saving immediately.",
Annotations: &sdk.ToolAnnotations{
Title: "Save recipe",
OpenWorldHint: &openWorld,
@@ -711,7 +717,7 @@ func changeShoppingListTool() *sdk.Tool {
return &sdk.Tool{
Name: changeShoppingListToolName,
Title: "Change shopping list",
- Description: "Change the Cooked shopping list. This slice supports action add with newline-separated ingredients and optional recipe_id, action remove, replace_selection, add_selection, or remove_selection with product_group_ids, action update_item with product_group_id and optional name, quantity, aisle_id, or selected, and action clear. Remove and clear are destructive. Other actions are reserved for later slices.",
+ Description: "Change the Cooked shopping list. Supports add, update_item, replace_selection, add_selection, remove_selection, remove, and clear. Add accepts newline-separated ingredients and optional recipe_id. Remove and clear are destructive.",
Annotations: &sdk.ToolAnnotations{
Title: "Change shopping list",
DestructiveHint: &destructive,
@@ -1016,7 +1022,7 @@ type PreviewRecipeTextOutput struct {
// SaveRecipeArguments contains save_recipe tool arguments.
type SaveRecipeArguments struct {
- Source string `json:"source" jsonschema:"Recipe save source. Supported now: raw_text, prepared, url, draft, and existing."`
+ 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."`
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."`
@@ -1047,7 +1053,7 @@ type DeleteRecipeOutput struct {
// ChangeShoppingListArguments contains change_shopping_list tool arguments.
type ChangeShoppingListArguments struct {
- Action string `json:"action" jsonschema:"Shopping-list action. Supported now: add, remove, replace_selection, add_selection, remove_selection, update_item, and clear."`
+ Action string `json:"action" jsonschema:"Shopping-list action: add, remove, replace_selection, add_selection, remove_selection, update_item, or clear."`
Ingredients string `json:"ingredients,omitempty" jsonschema:"Newline-separated ingredients for action add."`
RecipeID string `json:"recipe_id,omitempty" jsonschema:"Optional saved recipe ID or import draft ID for action add."`
ProductGroupIDs []string `json:"product_group_ids,omitempty" jsonschema:"Product group IDs for action remove, replace_selection, add_selection, or remove_selection."`
@@ -360,19 +360,6 @@ func TestCallToolACIDRecipesSave2_1To2_3RequiresPreparedFields(t *testing.T) {
}
}
-func TestCallToolACIDRecipesSave10RejectsUnsupportedSourceBeforeCallingCooked(t *testing.T) {
- backend := &fakeBackend{}
- server := NewServer(backend, "test")
-
- _, _, err := server.callSaveRecipeTool(context.Background(), nil, SaveRecipeArguments{Source: "unsupported"})
- if err == nil {
- t.Fatal("callSaveRecipeTool() error = nil, want unsupported source error")
- }
- if backend.saveCalls != 0 || backend.draftSaveCalls != 0 {
- t.Fatalf("save calls = prepared %d draft %d, want none", backend.saveCalls, backend.draftSaveCalls)
- }
-}
-
func TestCallToolACIDRecipesSave1And11SavesRawTextRecipe(t *testing.T) {
backend := &fakeBackend{
preview: cooked.RecipeTextPreview{
@@ -595,9 +582,14 @@ func TestCallToolACIDShoppingListActions2RejectsUnknownActionBeforeCallingCooked
tests := []struct {
name string
arguments ChangeShoppingListArguments
+ wantError string
}{
- {name: "empty", arguments: ChangeShoppingListArguments{Action: " "}},
- {name: "unknown", arguments: ChangeShoppingListArguments{Action: "dance"}},
+ {name: "empty", arguments: ChangeShoppingListArguments{Action: " "}, wantError: "action is required"},
+ {
+ name: "unknown",
+ arguments: ChangeShoppingListArguments{Action: "dance"},
+ wantError: `unsupported change_shopping_list action "dance" (supported: add, update_item, replace_selection, add_selection, remove_selection, remove, clear)`,
+ },
}
for _, tt := range tests {
@@ -609,6 +601,9 @@ func TestCallToolACIDShoppingListActions2RejectsUnknownActionBeforeCallingCooked
if err == nil {
t.Fatal("callChangeShoppingListTool() error = nil, want action error")
}
+ if err.Error() != tt.wantError {
+ t.Fatalf("callChangeShoppingListTool() error = %q", err.Error())
+ }
if backend.clearShoppingListCalls != 0 {
t.Fatalf("clear calls = %d, want none", backend.clearShoppingListCalls)
}
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+//
+// SPDX-License-Identifier: LicenseRef-MutuaL-1.2
+
+package mcp
+
+import (
+ "context"
+ "strings"
+ "testing"
+)
+
+// tools.READ_TOOL.1 tools.SCHEMA.4 tools.RESPONSES.6
+func TestCallToolACIDToolsReadTool1RejectsUnsupportedTargetBeforeCallingCooked(t *testing.T) {
+ backend := &fakeBackend{}
+ server := NewServer(backend, "test")
+
+ _, err := server.CallReadTool(context.Background(), ReadArguments{Target: "meal_plan"})
+ if err == nil {
+ t.Fatal("CallReadTool() error = nil, want unsupported target error")
+ }
+ if err.Error() != `unsupported read target "meal_plan" (supported: shopping_list, recipes, recipe)` {
+ t.Fatalf("CallReadTool() error = %q", err.Error())
+ }
+ if backend.readShoppingListCalls != 0 || backend.metadataCalls != 0 || backend.contentCalls != 0 {
+ t.Fatalf(
+ "backend calls = shopping list %d metadata %d content %d, want none",
+ backend.readShoppingListCalls,
+ backend.metadataCalls,
+ backend.contentCalls,
+ )
+ }
+}
+
+// recipes.SAVE.10 tools.SCHEMA.4 tools.RESPONSES.6
+func TestCallToolACIDRecipesSave10RejectsUnsupportedSourceBeforeCallingCooked(t *testing.T) {
+ backend := &fakeBackend{}
+ server := NewServer(backend, "test")
+
+ _, _, err := server.callSaveRecipeTool(context.Background(), nil, SaveRecipeArguments{Source: "unsupported"})
+ if err == nil {
+ t.Fatal("callSaveRecipeTool() error = nil, want unsupported source error")
+ }
+ if err.Error() != `unsupported save_recipe source "unsupported" (supported: raw_text, prepared, url, draft, existing)` {
+ t.Fatalf("callSaveRecipeTool() error = %q", err.Error())
+ }
+ if backend.saveCalls != 0 || backend.draftSaveCalls != 0 {
+ t.Fatalf("save calls = prepared %d draft %d, want none", backend.saveCalls, backend.draftSaveCalls)
+ }
+}
+
+// tools.SCHEMA.3 tools.SCHEMA.5 tools.SAVE_RECIPE_TOOL.1 tools.CHANGE_SHOPPING_LIST_TOOL.1
+func TestToolDescriptionsACIDToolsSchema3And5DescribeCurrentLimits(t *testing.T) {
+ save := saveRecipeTool()
+ for _, want := range []string{
+ "raw text",
+ "prepared markdown",
+ "URL import",
+ "reviewed URL-import draft",
+ "existing recipe update",
+ "draft_id",
+ } {
+ if !strings.Contains(save.Description, want) {
+ t.Fatalf("save_recipe description missing %q: %q", want, save.Description)
+ }
+ }
+
+ shopping := changeShoppingListTool()
+ for _, want := range []string{
+ "add",
+ "update_item",
+ "replace_selection",
+ "add_selection",
+ "remove_selection",
+ "remove",
+ "clear",
+ "Remove and clear are destructive",
+ } {
+ if !strings.Contains(shopping.Description, want) {
+ t.Fatalf("change_shopping_list description missing %q: %q", want, shopping.Description)
+ }
+ }
+}