cookbook-co-mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cookbook-mcp.js +46 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,7 @@ unsupported-protocol-version error that names the supported version.
|
|
|
125
125
|
| `COOKBOOK_TOKEN` | Required. The login token copied from the web app. |
|
|
126
126
|
| `COOKBOOK_READ_ONLY` | Set to `1` to register only the read tools. |
|
|
127
127
|
| `COOKBOOK_PORT` | Port for `serve` (or pass it as `serve <port>`). |
|
|
128
|
+
| `COOKBOOK_DEFAULT_SHOPPING_LIST` | Shopping list used when a tool is given none. Default `Shopping List`. |
|
|
128
129
|
| `COOKBOOK_BACKUP_DIR` | Where recipe backups go. Default `~/.local/state/cookbook-mcp/backups`. |
|
|
129
130
|
| `COOKBOOK_BACKUP_KEEP` | Edit backups kept per recipe. Default 20. |
|
|
130
131
|
| `COOKBOOK_EXPORT_DIR` | Where exports go when no path is given. Default `~/Downloads`, else `~/.local/state/cookbook-mcp/exports`. |
|
package/dist/cookbook-mcp.js
CHANGED
|
@@ -127978,14 +127978,14 @@ var RecipesApi = class {
|
|
|
127978
127978
|
}
|
|
127979
127979
|
};
|
|
127980
127980
|
function recipePatch(recipe, tags, fave = recipe.fave) {
|
|
127981
|
-
const { category: _category, instructions, created: _c, changed: _ch, ...rest } = recipe;
|
|
127981
|
+
const { category: _category, instructions: instructions2, created: _c, changed: _ch, ...rest } = recipe;
|
|
127982
127982
|
return {
|
|
127983
127983
|
...rest,
|
|
127984
127984
|
fave,
|
|
127985
127985
|
category: tags,
|
|
127986
127986
|
categoryNames: [],
|
|
127987
127987
|
// the app writes `directions` with zero-based id/item and a one-based itemId
|
|
127988
|
-
directions:
|
|
127988
|
+
directions: instructions2.map((d, i2) => ({
|
|
127989
127989
|
id: i2,
|
|
127990
127990
|
item: i2,
|
|
127991
127991
|
itemId: i2 + 1,
|
|
@@ -131900,7 +131900,7 @@ var updateRecipe = defineTool({
|
|
|
131900
131900
|
apply: external_exports.boolean().default(false).describe("false previews only; true saves"),
|
|
131901
131901
|
show_full: external_exports.boolean().default(false).describe("Preview only: also return the full recipe before and after, not just the changes")
|
|
131902
131902
|
},
|
|
131903
|
-
run: async ({ id, changed, ingredients, instructions, notes, servings, apply: apply2, show_full, ...fields }, { client, backup }) => {
|
|
131903
|
+
run: async ({ id, changed, ingredients, instructions: instructions2, notes, servings, apply: apply2, show_full, ...fields }, { client, backup }) => {
|
|
131904
131904
|
const recipe = await freshRecipe(client, id);
|
|
131905
131905
|
if (!recipe) return fail2(`No recipe with id ${id}`);
|
|
131906
131906
|
const stale = staleError(recipe, changed);
|
|
@@ -131908,11 +131908,11 @@ var updateRecipe = defineTool({
|
|
|
131908
131908
|
const edits = {
|
|
131909
131909
|
...pickBy({ ...fields, serving: servings }, (value) => value !== void 0),
|
|
131910
131910
|
...ingredients && { ingredients: toIngredients(ingredients) },
|
|
131911
|
-
...
|
|
131911
|
+
...instructions2 && { instructions: toInstructions(instructions2, recipe.instructions) },
|
|
131912
131912
|
...notes !== void 0 && { notes }
|
|
131913
131913
|
};
|
|
131914
131914
|
if (isEmpty2(edits)) return fail2("Nothing to change: pass at least one field to edit");
|
|
131915
|
-
const lostPhotos =
|
|
131915
|
+
const lostPhotos = instructions2 ? recipe.instructions.filter((s) => s.photo && !instructions2.includes(s.text)).length : 0;
|
|
131916
131916
|
const warnings = lostPhotos > 0 ? [`${lostPhotos} step photo(s) will be dropped because their step text changed`] : [];
|
|
131917
131917
|
if (ingredients && !isDrink(recipe, await client.tags.list())) {
|
|
131918
131918
|
const unclear = ambiguousOunces(edits.ingredients ?? []);
|
|
@@ -132145,12 +132145,19 @@ var aisleOverrides = external_exports.array(
|
|
|
132145
132145
|
function overrideMap(entries3) {
|
|
132146
132146
|
return new Map(entries3.map((e) => [normalizeName(e.item), aisleId(e.aisle)]));
|
|
132147
132147
|
}
|
|
132148
|
-
var list = external_exports.string().optional().describe(
|
|
132148
|
+
var list = external_exports.string().optional().describe("List name or id; defaults to the default list (marked by list_shopping_lists)");
|
|
132149
132149
|
var on_hand = external_exports.array(external_exports.string()).default([]).describe("Extra ingredient names assumed on hand and skipped");
|
|
132150
132150
|
var include_staples = external_exports.boolean().default(false).describe("Also add staples (water, salt, pepper, cooking oil and spray) normally skipped");
|
|
132151
132151
|
var apply = external_exports.boolean().default(false).describe("false previews only; true makes the change");
|
|
132152
|
-
var DEFAULT_LIST = "
|
|
132153
|
-
|
|
132152
|
+
var DEFAULT_LIST = "Shopping List";
|
|
132153
|
+
function findDefaultList(lists, configured) {
|
|
132154
|
+
const key = (configured ?? DEFAULT_LIST).toLowerCase();
|
|
132155
|
+
const match = lists.find((l) => l.id === configured || l.name.toLowerCase() === key);
|
|
132156
|
+
if (match || configured) return match;
|
|
132157
|
+
const others = lists.filter((l) => l.id !== "common");
|
|
132158
|
+
return others.length === 1 ? others[0] : void 0;
|
|
132159
|
+
}
|
|
132160
|
+
async function resolveList(client, ref, configured) {
|
|
132154
132161
|
const lists = await client.shopping.lists();
|
|
132155
132162
|
const names = lists.map((l) => l.name).join(", ");
|
|
132156
132163
|
if (ref) {
|
|
@@ -132163,11 +132170,9 @@ async function resolveList(client, ref) {
|
|
|
132163
132170
|
error: partial2.length === 0 ? `No list "${ref}". Lists: ${names}` : `"${ref}" matches several lists (${partial2.map((l) => l.name).join(", ")}); use the full name`
|
|
132164
132171
|
};
|
|
132165
132172
|
}
|
|
132166
|
-
const
|
|
132167
|
-
if (
|
|
132168
|
-
|
|
132169
|
-
if (others.length === 1) return others[0];
|
|
132170
|
-
return { error: `No "Shopping List" found; pass \`list\`. Lists: ${names}` };
|
|
132173
|
+
const found = findDefaultList(lists, configured);
|
|
132174
|
+
if (found) return found;
|
|
132175
|
+
return { error: `No "${configured ?? DEFAULT_LIST}" found; pass \`list\`. Lists: ${names}` };
|
|
132171
132176
|
}
|
|
132172
132177
|
async function applyPlan(client, listId, plan) {
|
|
132173
132178
|
await client.shopping.addItems(listId, plan.adds);
|
|
@@ -132218,8 +132223,8 @@ var addPlannedMealsToShoppingList = defineTool({
|
|
|
132218
132223
|
aisles: aisleOverrides,
|
|
132219
132224
|
apply
|
|
132220
132225
|
},
|
|
132221
|
-
run: async ({ from, to, list: ref, on_hand: extra, include_staples: staples, aisles, apply: go }, { client }) => {
|
|
132222
|
-
const found = await resolveList(client, ref);
|
|
132226
|
+
run: async ({ from, to, list: ref, on_hand: extra, include_staples: staples, aisles, apply: go }, { client, config: config2 }) => {
|
|
132227
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132223
132228
|
if ("error" in found) return fail2(found.error);
|
|
132224
132229
|
const [planned, recipes] = await Promise.all([
|
|
132225
132230
|
client.mealPlan.list(from, to, true),
|
|
@@ -132275,8 +132280,8 @@ var addRecipeToShoppingList = defineTool({
|
|
|
132275
132280
|
aisles: aisleOverrides,
|
|
132276
132281
|
apply
|
|
132277
132282
|
},
|
|
132278
|
-
run: async ({ recipe_id, servings, list: ref, on_hand: extra, include_staples: staples, aisles, apply: go }, { client }) => {
|
|
132279
|
-
const found = await resolveList(client, ref);
|
|
132283
|
+
run: async ({ recipe_id, servings, list: ref, on_hand: extra, include_staples: staples, aisles, apply: go }, { client, config: config2 }) => {
|
|
132284
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132280
132285
|
if ("error" in found) return fail2(found.error);
|
|
132281
132286
|
const recipe = (await client.recipes.list()).find((r) => r.id === recipe_id);
|
|
132282
132287
|
if (!recipe) return fail2(`No recipe with id ${recipe_id}`);
|
|
@@ -132318,8 +132323,8 @@ var addToShoppingList = defineTool({
|
|
|
132318
132323
|
).min(1).describe("Items to add; the same name and unit as an open item adds to its quantity"),
|
|
132319
132324
|
list
|
|
132320
132325
|
},
|
|
132321
|
-
run: async ({ items, list: ref }, { client }) => {
|
|
132322
|
-
const found = await resolveList(client, ref);
|
|
132326
|
+
run: async ({ items, list: ref }, { client, config: config2 }) => {
|
|
132327
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132323
132328
|
if ("error" in found) return fail2(found.error);
|
|
132324
132329
|
const lines = items.map((i2) => ({
|
|
132325
132330
|
name: i2.name.trim(),
|
|
@@ -132352,8 +132357,8 @@ function itemActionTool(options2) {
|
|
|
132352
132357
|
),
|
|
132353
132358
|
list
|
|
132354
132359
|
},
|
|
132355
|
-
run: async ({ items: queries, list: ref }, { client }) => {
|
|
132356
|
-
const found = await resolveList(client, ref);
|
|
132360
|
+
run: async ({ items: queries, list: ref }, { client, config: config2 }) => {
|
|
132361
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132357
132362
|
if ("error" in found) return fail2(found.error);
|
|
132358
132363
|
const all = await client.shopping.items(found.id);
|
|
132359
132364
|
const picked = [];
|
|
@@ -132393,8 +132398,8 @@ var clearCompleted = defineTool({
|
|
|
132393
132398
|
description: "Delete every checked-off item from a shopping list. The open items stay. This cannot be undone.",
|
|
132394
132399
|
hints: ["destructive", "idempotent"],
|
|
132395
132400
|
inputSchema: { list },
|
|
132396
|
-
run: async ({ list: ref }, { client }) => {
|
|
132397
|
-
const found = await resolveList(client, ref);
|
|
132401
|
+
run: async ({ list: ref }, { client, config: config2 }) => {
|
|
132402
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132398
132403
|
if ("error" in found) return fail2(found.error);
|
|
132399
132404
|
const done = (await client.shopping.items(found.id)).filter((i2) => i2.complete);
|
|
132400
132405
|
await client.shopping.deleteItems(
|
|
@@ -132430,8 +132435,8 @@ var deleteShoppingList = defineTool({
|
|
|
132430
132435
|
name: external_exports.string().describe("The list's current name, to confirm"),
|
|
132431
132436
|
apply
|
|
132432
132437
|
},
|
|
132433
|
-
run: async ({ list: ref, name: name5, apply: go }, { client }) => {
|
|
132434
|
-
const found = await resolveList(client, ref);
|
|
132438
|
+
run: async ({ list: ref, name: name5, apply: go }, { client, config: config2 }) => {
|
|
132439
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132435
132440
|
if ("error" in found) return fail2(found.error);
|
|
132436
132441
|
if (found.id === "common") return fail2('The built-in "Regular items" list cannot be deleted');
|
|
132437
132442
|
if (found.name.toLowerCase() !== name5.toLowerCase()) {
|
|
@@ -132465,8 +132470,8 @@ var getShoppingList = defineTool({
|
|
|
132465
132470
|
ids: external_exports.boolean().default(false).describe("Return each item as {id, text} instead of text"),
|
|
132466
132471
|
show_done: external_exports.boolean().default(false).describe("List the checked-off items, not just a count")
|
|
132467
132472
|
},
|
|
132468
|
-
run: async ({ list: ref, ids, show_done }, { client }) => {
|
|
132469
|
-
const found = await resolveList(client, ref);
|
|
132473
|
+
run: async ({ list: ref, ids, show_done }, { client, config: config2 }) => {
|
|
132474
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132470
132475
|
if ("error" in found) return fail2(found.error);
|
|
132471
132476
|
const items = await client.shopping.items(found.id);
|
|
132472
132477
|
const order = Object.values(AISLES);
|
|
@@ -132506,17 +132511,19 @@ var listLearnedAisles = defineTool({
|
|
|
132506
132511
|
// src/tools/shopping/list-shopping-lists.ts
|
|
132507
132512
|
var listShoppingLists = defineTool({
|
|
132508
132513
|
name: "list_shopping_lists",
|
|
132509
|
-
description: `List the shopping lists by name with open and done item counts.
|
|
132514
|
+
description: `List the shopping lists by name with open and done item counts. The default list, used when a tool is given no list, is marked default: true; "Regular items" is the app's built-in list of regular groceries.`,
|
|
132510
132515
|
hints: ["readOnly"],
|
|
132511
132516
|
inputSchema: {},
|
|
132512
|
-
run: async (_args, { client }) => {
|
|
132517
|
+
run: async (_args, { client, config: config2 }) => {
|
|
132513
132518
|
const lists = await client.shopping.lists();
|
|
132519
|
+
const defaultList = findDefaultList(lists, config2.defaultShoppingList);
|
|
132514
132520
|
return json2(
|
|
132515
132521
|
await Promise.all(
|
|
132516
132522
|
lists.map(async (l) => {
|
|
132517
132523
|
const items = await client.shopping.items(l.id);
|
|
132518
132524
|
return {
|
|
132519
132525
|
name: l.name,
|
|
132526
|
+
default: l === defaultList,
|
|
132520
132527
|
open: items.filter((i2) => !i2.complete).length,
|
|
132521
132528
|
done: items.filter((i2) => i2.complete).length
|
|
132522
132529
|
};
|
|
@@ -132546,8 +132553,8 @@ var setItemAisle = defineTool({
|
|
|
132546
132553
|
list,
|
|
132547
132554
|
remember: external_exports.boolean().default(true).describe("Also remember this aisle for future adds")
|
|
132548
132555
|
},
|
|
132549
|
-
run: async ({ items: queries, aisle, list: ref, remember }, { client }) => {
|
|
132550
|
-
const found = await resolveList(client, ref);
|
|
132556
|
+
run: async ({ items: queries, aisle, list: ref, remember }, { client, config: config2 }) => {
|
|
132557
|
+
const found = await resolveList(client, ref, config2.defaultShoppingList);
|
|
132551
132558
|
if ("error" in found) return fail2(found.error);
|
|
132552
132559
|
const all = await client.shopping.items(found.id);
|
|
132553
132560
|
const picked = [];
|
|
@@ -132759,6 +132766,7 @@ function loadConfig(env = process.env, home = homedir()) {
|
|
|
132759
132766
|
exportDirs: env.COOKBOOK_EXPORT_DIR ? [env.COOKBOOK_EXPORT_DIR] : [join3(home, "Downloads"), join3(state, "exports")],
|
|
132760
132767
|
backupDir: env.COOKBOOK_BACKUP_DIR ?? join3(state, "backups"),
|
|
132761
132768
|
backupKeep: Math.max(1, Number(env.COOKBOOK_BACKUP_KEEP ?? 20) || 20),
|
|
132769
|
+
defaultShoppingList: env.COOKBOOK_DEFAULT_SHOPPING_LIST?.trim() || void 0,
|
|
132762
132770
|
cache: loadCacheSettings(env)
|
|
132763
132771
|
};
|
|
132764
132772
|
}
|
|
@@ -132799,16 +132807,17 @@ function parseWindow(variable, raw, canBeOff) {
|
|
|
132799
132807
|
}
|
|
132800
132808
|
|
|
132801
132809
|
// src/server/instructions.ts
|
|
132802
|
-
function serverInstructions(
|
|
132803
|
-
|
|
132804
|
-
|
|
132810
|
+
function serverInstructions(config2) {
|
|
132811
|
+
const text2 = instructions(config2.defaultShoppingList ?? DEFAULT_LIST);
|
|
132812
|
+
return config2.readOnly ? `${text2}
|
|
132813
|
+
${READ_ONLY_NOTE}` : text2;
|
|
132805
132814
|
}
|
|
132806
132815
|
var READ_ONLY_NOTE = "READ-ONLY MODE: only tools that read are available; nothing can be created, changed or deleted.";
|
|
132807
|
-
var
|
|
132816
|
+
var instructions = (defaultList) => `CookBook recipe manager: the user's personal recipe library, tags and meal planner.
|
|
132808
132817
|
Find recipes with search_recipes or get_recent_recipes, then read one with get_recipe.
|
|
132809
132818
|
scale_recipe shows scaled ingredient amounts without saving anything.
|
|
132810
132819
|
set_recipe_photo replaces a recipe's main photo from a file path or URL.
|
|
132811
|
-
Shopping lists: "
|
|
132820
|
+
Shopping lists: "${defaultList}" is the default list; recipe and planned-meal adds skip staples (water, salt, pepper, cooking oils) and report them, and preview before writing.
|
|
132812
132821
|
Aisles for new items come from the app's remembered products, else a crude keyword guess: pass the aisle yourself whenever you know it, and fix a wrong one with set_item_aisle or teach_aisle.
|
|
132813
132822
|
Cooking and rating are separate: mark_cooked logs a cook (adds to the count, sets the last-cooked date, optional rating and date); rate_recipe only sets the stars.
|
|
132814
132823
|
Edit tools (set_tags, set_favourite, rate_recipe, rename_recipe, set_recipe_photo, update_recipe) need that recipe's \`changed\` value from get_recipe and refuse if it is stale.
|
|
@@ -132830,7 +132839,7 @@ function createServerFactory(log) {
|
|
|
132830
132839
|
const create2 = () => {
|
|
132831
132840
|
const server = new McpServer(
|
|
132832
132841
|
{ name: "cookbook", version: "0.1.0" },
|
|
132833
|
-
{ instructions: serverInstructions(config2
|
|
132842
|
+
{ instructions: serverInstructions(config2) }
|
|
132834
132843
|
);
|
|
132835
132844
|
registerTools(server, context);
|
|
132836
132845
|
return server;
|