@bojanrajkovic/mcp-paprika 1.2.0-beta.1 → 1.2.0-beta.2
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/dist/paprika/client.js
CHANGED
|
@@ -22,6 +22,20 @@ class TransientHTTPError extends Error {
|
|
|
22
22
|
this.name = "TransientHTTPError";
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
// Private marker class used to route network-level fetch failures (DNS,
|
|
26
|
+
// TCP reset, TLS handshake, etc.) through cockatiel's handleType-based
|
|
27
|
+
// retry policy. undici throws a bare TypeError for these; the runtime
|
|
28
|
+
// has no dedicated subclass we can match on directly. The cause is the
|
|
29
|
+
// original TypeError so callers can unwrap and surface the real error
|
|
30
|
+
// once retries are exhausted.
|
|
31
|
+
class NetworkRetryableError extends Error {
|
|
32
|
+
cause;
|
|
33
|
+
constructor(cause) {
|
|
34
|
+
super(`Network error: ${cause.message}`, { cause });
|
|
35
|
+
this.cause = cause;
|
|
36
|
+
this.name = "NetworkRetryableError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
25
39
|
class TokenExpiredError extends Error {
|
|
26
40
|
constructor() {
|
|
27
41
|
super("Token expired");
|
|
@@ -29,14 +43,14 @@ class TokenExpiredError extends Error {
|
|
|
29
43
|
}
|
|
30
44
|
}
|
|
31
45
|
const RETRYABLE_STATUSES = new Set([429, 500, 502, 503]);
|
|
32
|
-
const retryPolicy = retry(handleType(TransientHTTPError), {
|
|
46
|
+
const retryPolicy = retry(handleType(TransientHTTPError).orType(NetworkRetryableError), {
|
|
33
47
|
maxAttempts: 3,
|
|
34
48
|
backoff: new ExponentialBackoff({
|
|
35
49
|
initialDelay: 500,
|
|
36
50
|
maxDelay: 10_000,
|
|
37
51
|
}),
|
|
38
52
|
});
|
|
39
|
-
const breakerPolicy = circuitBreaker(handleType(TransientHTTPError), {
|
|
53
|
+
const breakerPolicy = circuitBreaker(handleType(TransientHTTPError).orType(NetworkRetryableError), {
|
|
40
54
|
halfOpenAfter: 30_000,
|
|
41
55
|
breaker: new ConsecutiveBreaker(5),
|
|
42
56
|
});
|
|
@@ -178,7 +192,20 @@ export class PaprikaClient {
|
|
|
178
192
|
if (body !== undefined) {
|
|
179
193
|
fetchInit.body = body;
|
|
180
194
|
}
|
|
181
|
-
|
|
195
|
+
let response;
|
|
196
|
+
try {
|
|
197
|
+
response = await fetch(url, fetchInit);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
// undici throws a bare TypeError for network-level failures (DNS,
|
|
201
|
+
// TCP reset, TLS handshake, abort). Re-throw as a retryable marker
|
|
202
|
+
// so cockatiel's handleType policy applies the same backoff +
|
|
203
|
+
// circuit-breaker treatment as 5xx HTTP responses.
|
|
204
|
+
if (error instanceof TypeError) {
|
|
205
|
+
throw new NetworkRetryableError(error);
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
182
209
|
if (!response.ok) {
|
|
183
210
|
if (RETRYABLE_STATUSES.has(response.status)) {
|
|
184
211
|
throw new TransientHTTPError(response.status);
|
|
@@ -199,6 +226,11 @@ export class PaprikaClient {
|
|
|
199
226
|
if (error instanceof BrokenCircuitError) {
|
|
200
227
|
throw new PaprikaAPIError("Service unavailable (circuit open)", 503, url);
|
|
201
228
|
}
|
|
229
|
+
// Unwrap the retry marker so callers see the original undici TypeError —
|
|
230
|
+
// tools that surface .message stay consistent with the pre-retry shape.
|
|
231
|
+
if (error instanceof NetworkRetryableError) {
|
|
232
|
+
throw error.cause;
|
|
233
|
+
}
|
|
202
234
|
if (error instanceof TokenExpiredError) {
|
|
203
235
|
if (!this.token) {
|
|
204
236
|
throw new PaprikaAuthError("Authentication required (HTTP 401)");
|
|
@@ -214,6 +246,9 @@ export class PaprikaClient {
|
|
|
214
246
|
if (retryError instanceof BrokenCircuitError) {
|
|
215
247
|
throw new PaprikaAPIError("Service unavailable (circuit open)", 503, url);
|
|
216
248
|
}
|
|
249
|
+
if (retryError instanceof NetworkRetryableError) {
|
|
250
|
+
throw retryError.cause;
|
|
251
|
+
}
|
|
217
252
|
throw retryError;
|
|
218
253
|
}
|
|
219
254
|
}
|
package/dist/tools/create.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { toMessage } from "../utils/log.js";
|
|
1
|
+
import { createLogger, toMessage } from "../utils/log.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { RecipeUidSchema } from "../paprika/types.js";
|
|
4
4
|
import { coldStartGuard, commitRecipe, recipeToMarkdown, resolveCategoryNames, textResult } from "./helpers.js";
|
|
5
|
+
const log = createLogger("mcp-paprika:create_recipe");
|
|
5
6
|
export function registerCreateTool(server, ctx) {
|
|
6
7
|
server.registerTool("create_recipe", {
|
|
7
8
|
description: "Create a new recipe in the Paprika account.",
|
|
@@ -69,7 +70,9 @@ export function registerCreateTool(server, ctx) {
|
|
|
69
70
|
}
|
|
70
71
|
catch (error) {
|
|
71
72
|
// AC2.8: store/cache not updated — commitRecipe not reached
|
|
72
|
-
|
|
73
|
+
const message = toMessage(error);
|
|
74
|
+
log(`saveRecipe failed for name=${args.name}: ${message}`);
|
|
75
|
+
return textResult(`Failed to create recipe: ${message}`);
|
|
73
76
|
}
|
|
74
77
|
const categoryNames = ctx.store.resolveCategories(saved.categories);
|
|
75
78
|
const markdown = recipeToMarkdown(saved, categoryNames);
|
package/dist/tools/delete.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { toMessage } from "../utils/log.js";
|
|
1
|
+
import { createLogger, toMessage } from "../utils/log.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { RecipeUidSchema } from "../paprika/types.js";
|
|
4
4
|
import { coldStartGuard, commitRecipe, textResult } from "./helpers.js";
|
|
5
|
+
const log = createLogger("mcp-paprika:delete_recipe");
|
|
5
6
|
export function registerDeleteTool(server, ctx) {
|
|
6
7
|
server.registerTool("delete_recipe", {
|
|
7
8
|
description: "Soft-delete a recipe by UID, moving it to the Paprika trash. " +
|
|
@@ -26,7 +27,9 @@ export function registerDeleteTool(server, ctx) {
|
|
|
26
27
|
await commitRecipe(ctx, saved);
|
|
27
28
|
}
|
|
28
29
|
catch (error) {
|
|
29
|
-
|
|
30
|
+
const message = toMessage(error);
|
|
31
|
+
log(`saveRecipe (soft-delete) failed for uid=${trashed.uid}: ${message}`);
|
|
32
|
+
return textResult(`Failed to delete recipe: ${message}`);
|
|
30
33
|
}
|
|
31
34
|
return textResult(`Recipe "${recipe.name}" has been moved to the trash.`);
|
|
32
35
|
}, (guard) => guard);
|
package/dist/tools/pantry-add.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// pattern: Imperative Shell
|
|
2
|
-
import { toMessage } from "../utils/log.js";
|
|
2
|
+
import { createLogger, toMessage } from "../utils/log.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { PantryItemUidSchema } from "../paprika/types.js";
|
|
5
5
|
import { normalizePaprikaDate, paprikaDateToday } from "../paprika/dates.js";
|
|
6
6
|
import { textResult } from "./helpers.js";
|
|
7
7
|
import { commitPantryItem, pantryItemToMarkdown, pantryStartGuard } from "./pantry-helpers.js";
|
|
8
|
+
const log = createLogger("mcp-paprika:add_pantry_item");
|
|
8
9
|
export function registerAddPantryItemTool(server, ctx) {
|
|
9
10
|
server.registerTool("add_pantry_item", {
|
|
10
11
|
description: "Add a new item to the pantry. Rejects duplicates by case-insensitive ingredient name; " +
|
|
@@ -64,7 +65,9 @@ export function registerAddPantryItemTool(server, ctx) {
|
|
|
64
65
|
}
|
|
65
66
|
catch (error) {
|
|
66
67
|
// AC4.7: store/cache not updated — commitPantryItem not reached
|
|
67
|
-
|
|
68
|
+
const message = toMessage(error);
|
|
69
|
+
log(`savePantryItem failed for ${args.ingredient}: ${message}`);
|
|
70
|
+
return textResult(`Failed to add pantry item: ${message}`);
|
|
68
71
|
}
|
|
69
72
|
return textResult(pantryItemToMarkdown(saved));
|
|
70
73
|
}, (guard) => guard);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { toMessage } from "../utils/log.js";
|
|
1
|
+
import { createLogger, toMessage } from "../utils/log.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { PantryItemUidSchema } from "../paprika/types.js";
|
|
4
4
|
import { textResult } from "./helpers.js";
|
|
5
5
|
import { commitPantryItem, pantryStartGuard } from "./pantry-helpers.js";
|
|
6
|
+
const log = createLogger("mcp-paprika:delete_pantry_item");
|
|
6
7
|
export function registerDeletePantryItemTool(server, ctx) {
|
|
7
8
|
server.registerTool("delete_pantry_item", {
|
|
8
9
|
description: "Soft-delete a pantry item by UID. Idempotent: a second delete on the same UID " +
|
|
@@ -37,7 +38,9 @@ export function registerDeletePantryItemTool(server, ctx) {
|
|
|
37
38
|
await commitPantryItem(ctx, saved);
|
|
38
39
|
}
|
|
39
40
|
catch (error) {
|
|
40
|
-
|
|
41
|
+
const message = toMessage(error);
|
|
42
|
+
log(`savePantryItem (soft-delete) failed for uid=${trashed.uid}: ${message}`);
|
|
43
|
+
return textResult(`Failed to delete pantry item: ${message}`);
|
|
41
44
|
}
|
|
42
45
|
return textResult(`Pantry item "${existing.ingredient}" has been deleted.`);
|
|
43
46
|
}, (guard) => guard);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { toMessage } from "../utils/log.js";
|
|
1
|
+
import { createLogger, toMessage } from "../utils/log.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { PantryItemUidSchema } from "../paprika/types.js";
|
|
4
4
|
import { normalizePaprikaDate } from "../paprika/dates.js";
|
|
5
5
|
import { textResult } from "./helpers.js";
|
|
6
6
|
import { commitPantryItem, pantryItemToMarkdown, pantryStartGuard } from "./pantry-helpers.js";
|
|
7
|
+
const log = createLogger("mcp-paprika:update_pantry_item");
|
|
7
8
|
export function registerUpdatePantryItemTool(server, ctx) {
|
|
8
9
|
server.registerTool("update_pantry_item", {
|
|
9
10
|
description: "Update an existing pantry item by UID. Only provided fields are changed; " +
|
|
@@ -64,7 +65,9 @@ export function registerUpdatePantryItemTool(server, ctx) {
|
|
|
64
65
|
await commitPantryItem(ctx, saved);
|
|
65
66
|
}
|
|
66
67
|
catch (error) {
|
|
67
|
-
|
|
68
|
+
const message = toMessage(error);
|
|
69
|
+
log(`savePantryItem failed for uid=${updated.uid}: ${message}`);
|
|
70
|
+
return textResult(`Failed to update pantry item: ${message}`);
|
|
68
71
|
}
|
|
69
72
|
return textResult(pantryItemToMarkdown(saved));
|
|
70
73
|
}, (guard) => guard);
|
package/dist/tools/update.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { toMessage } from "../utils/log.js";
|
|
1
|
+
import { createLogger, toMessage } from "../utils/log.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { RecipeUidSchema } from "../paprika/types.js";
|
|
4
4
|
import { coldStartGuard, commitRecipe, recipeToMarkdown, resolveCategoryNames, textResult } from "./helpers.js";
|
|
5
|
+
const log = createLogger("mcp-paprika:update_recipe");
|
|
5
6
|
export function registerUpdateTool(server, ctx) {
|
|
6
7
|
server.registerTool("update_recipe", {
|
|
7
8
|
description: "Update an existing recipe by UID. Only provided fields are changed; " +
|
|
@@ -67,7 +68,9 @@ export function registerUpdateTool(server, ctx) {
|
|
|
67
68
|
await commitRecipe(ctx, saved); // AC3.4
|
|
68
69
|
}
|
|
69
70
|
catch (error) {
|
|
70
|
-
|
|
71
|
+
const message = toMessage(error);
|
|
72
|
+
log(`saveRecipe failed for uid=${updated.uid}: ${message}`);
|
|
73
|
+
return textResult(`Failed to update recipe: ${message}`);
|
|
71
74
|
}
|
|
72
75
|
const categoryNames = ctx.store.resolveCategories(saved.categories);
|
|
73
76
|
const markdown = recipeToMarkdown(saved, categoryNames);
|