picnic-mcp 0.3.0 → 0.4.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 +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/cart.d.ts +1 -1
- package/dist/tools/cart.js +10 -4
- package/dist/tools/cart.js.map +1 -1
- package/dist/tools/context.d.ts +5 -0
- package/dist/tools/context.js +9 -0
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/product.d.ts +1 -1
- package/dist/tools/product.js +5 -1
- package/dist/tools/product.js.map +1 -1
- package/dist/tools/replacements.d.ts +6 -2
- package/dist/tools/replacements.js +14 -7
- package/dist/tools/replacements.js.map +1 -1
- package/dist/tools/result.d.ts +9 -2
- package/dist/tools/result.js +11 -3
- package/dist/tools/result.js.map +1 -1
- package/dist/tools/search-products.js +12 -8
- package/dist/tools/search-products.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -72,3 +72,7 @@ await server.connect(transport);
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
Pass `tools` to choose the tool groups (e.g. `tools: ["cart", "recipes"]`), `client` instead of `authKey` to use a picnic-api client you already have, and `onError` to handle errors from Picnic yourself (by default they're logged to stderr).
|
|
75
|
+
|
|
76
|
+
### Product images
|
|
77
|
+
|
|
78
|
+
Results that list products (search, the cart, an added product, product details) carry the image URLs of those products in `_meta["picnic-mcp/images"]`, by product id. MCP clients don't pass `_meta` to the model, so the images cost no tokens; your own code can use them, e.g. to show product photos. The images don't need the auth key. The key is exported as `IMAGES_META_KEY`.
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export type { CountryCode } from "./types.js";
|
|
|
3
3
|
export type { ProductOutput } from "./format/products.js";
|
|
4
4
|
export type { CartOutput, CartLineOutput, CartSummaryOutput } from "./format/cart.js";
|
|
5
5
|
export { DEFAULT_TOOL_GROUPS, TOOL_GROUPS, type ToolGroup } from "./tools/groups.js";
|
|
6
|
+
export { IMAGES_META_KEY, type ImageUrls } from "./tools/result.js";
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA4B,MAAM,aAAa,CAAC;AAIrE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA4B,MAAM,aAAa,CAAC;AAIrE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,eAAe,EAAkB,MAAM,mBAAmB,CAAC"}
|
package/dist/tools/cart.d.ts
CHANGED
package/dist/tools/cart.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { formatCart, formatCartSummary, formatUnavailability } from "../format/cart.js";
|
|
3
|
+
import { imageUrls } from "./context.js";
|
|
3
4
|
import { productId, quantity } from "./inputs.js";
|
|
4
5
|
import { loadReplacements } from "./replacements.js";
|
|
5
6
|
import { errorResult, jsonResult, runTool } from "./result.js";
|
|
@@ -12,7 +13,11 @@ export function registerCartTools(server, context) {
|
|
|
12
13
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
13
14
|
}, () => runTool(async () => {
|
|
14
15
|
const cart = await client.cart.getCart();
|
|
15
|
-
|
|
16
|
+
const replacements = await loadReplacements(context, cart);
|
|
17
|
+
return jsonResult(formatCart(cart, countryCode, replacements.byProduct), {
|
|
18
|
+
...imageUrls(context, cart.items),
|
|
19
|
+
...replacements.images,
|
|
20
|
+
});
|
|
16
21
|
}, onError));
|
|
17
22
|
server.registerTool("add_to_cart", {
|
|
18
23
|
title: "Add to cart",
|
|
@@ -30,7 +35,7 @@ export function registerCartTools(server, context) {
|
|
|
30
35
|
}
|
|
31
36
|
const unavailableLine = lines.find((line) => line.isUnavailable);
|
|
32
37
|
const replacements = unavailableLine
|
|
33
|
-
?
|
|
38
|
+
? await loadReplacements(context, cart, [productId])
|
|
34
39
|
: undefined;
|
|
35
40
|
return jsonResult({
|
|
36
41
|
...(unavailableLine && {
|
|
@@ -40,10 +45,11 @@ export function registerCartTools(server, context) {
|
|
|
40
45
|
productId,
|
|
41
46
|
name: lines[0].name,
|
|
42
47
|
quantityInCart: quantityOf(lines),
|
|
43
|
-
...(unavailableLine &&
|
|
48
|
+
...(unavailableLine &&
|
|
49
|
+
formatUnavailability(unavailableLine, replacements?.byProduct.get(productId))),
|
|
44
50
|
},
|
|
45
51
|
cart: formatCartSummary(cart, countryCode),
|
|
46
|
-
});
|
|
52
|
+
}, { ...imageUrls(context, lines.slice(0, 1)), ...replacements?.images });
|
|
47
53
|
}, onError));
|
|
48
54
|
server.registerTool("remove_from_cart", {
|
|
49
55
|
title: "Remove from cart",
|
package/dist/tools/cart.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart.js","sourceRoot":"","sources":["../../src/tools/cart.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"cart.js","sourceRoot":"","sources":["../../src/tools/cart.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/context.d.ts
CHANGED
|
@@ -8,3 +8,8 @@ export type ToolContext = {
|
|
|
8
8
|
};
|
|
9
9
|
/** The language Picnic's search expects in each country. */
|
|
10
10
|
export declare const SEARCH_LANGUAGES: Record<CountryCode, string>;
|
|
11
|
+
/** The image URLs of products, by product id, leaving out products without an image. */
|
|
12
|
+
export declare function imageUrls({ client }: ToolContext, products: Iterable<{
|
|
13
|
+
productId: string;
|
|
14
|
+
imageId: string | null;
|
|
15
|
+
}>): Record<string, string>;
|
package/dist/tools/context.js
CHANGED
|
@@ -4,4 +4,13 @@ export const SEARCH_LANGUAGES = {
|
|
|
4
4
|
DE: "German",
|
|
5
5
|
FR: "French",
|
|
6
6
|
};
|
|
7
|
+
/** The image URLs of products, by product id, leaving out products without an image. */
|
|
8
|
+
export function imageUrls({ client }, products) {
|
|
9
|
+
const urls = {};
|
|
10
|
+
for (const { productId, imageId } of products) {
|
|
11
|
+
if (imageId)
|
|
12
|
+
urls[productId] = client.catalog.getImageUrl(imageId, "large");
|
|
13
|
+
}
|
|
14
|
+
return urls;
|
|
15
|
+
}
|
|
7
16
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"AAWA,4DAA4D;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAgC;IAC3D,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;CACb,CAAC"}
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"AAWA,4DAA4D;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAgC;IAC3D,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,QAAQ;CACb,CAAC;AAEF,wFAAwF;AACxF,MAAM,UAAU,SAAS,CACvB,EAAE,MAAM,EAAe,EACvB,QAAiE;IAEjE,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,KAAK,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,IAAI,OAAO;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/tools/product.d.ts
CHANGED
package/dist/tools/product.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { formatProductDetails } from "../format/product.js";
|
|
3
|
+
import { imageUrls } from "./context.js";
|
|
3
4
|
import { productId } from "./inputs.js";
|
|
4
5
|
import { jsonResult, runTool } from "./result.js";
|
|
5
6
|
export function registerProductTools(server, context) {
|
|
@@ -10,6 +11,9 @@ export function registerProductTools(server, context) {
|
|
|
10
11
|
"Use it for questions such as whether a product contains gluten. Get the product id from search_products or get_cart.",
|
|
11
12
|
inputSchema: z.object({ productId }),
|
|
12
13
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
13
|
-
}, ({ productId }) => runTool(async () =>
|
|
14
|
+
}, ({ productId }) => runTool(async () => {
|
|
15
|
+
const product = await client.catalog.getProduct(productId);
|
|
16
|
+
return jsonResult(formatProductDetails(product, countryCode), imageUrls(context, [{ productId: product.id, imageId: product.imageIds[0] ?? null }]));
|
|
17
|
+
}, onError));
|
|
14
18
|
}
|
|
15
19
|
//# sourceMappingURL=product.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/tools/product.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/tools/product.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,OAAoB;IAC1E,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAEjD,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,6GAA6G;YAC7G,sHAAsH;QACxH,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;QACpC,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KACzD,EACD,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAChB,OAAO,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC3D,OAAO,UAAU,CACf,oBAAoB,CAAC,OAAO,EAAE,WAAW,CAAC,EAC1C,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CACtF,CAAC;IACJ,CAAC,EAAE,OAAO,CAAC,CACd,CAAC;AACJ,CAAC"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { type ProductOutput } from "../format/products.js";
|
|
2
2
|
import type { Cart } from "../types.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type ToolContext } from "./context.js";
|
|
4
|
+
import type { ImageUrls } from "./result.js";
|
|
4
5
|
/**
|
|
5
6
|
* Reads the replacements of the given products that can't be delivered, as products to offer. A
|
|
6
7
|
* product whose replacements can't be read is left out: the model still sees why it can't be
|
|
7
8
|
* delivered.
|
|
8
9
|
*/
|
|
9
|
-
export declare function loadReplacements(
|
|
10
|
+
export declare function loadReplacements(context: ToolContext, cart: Cart, productIds?: readonly string[]): Promise<{
|
|
11
|
+
byProduct: Map<string, ProductOutput[]>;
|
|
12
|
+
images: ImageUrls;
|
|
13
|
+
}>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatProduct } from "../format/products.js";
|
|
2
|
+
import { imageUrls } from "./context.js";
|
|
2
3
|
/** At most this many products' replacements are read, each one request. */
|
|
3
4
|
const MAX_PRODUCTS = 3;
|
|
4
5
|
/** At most this many replacements per product. */
|
|
@@ -8,7 +9,8 @@ const MAX_REPLACEMENTS = 3;
|
|
|
8
9
|
* product whose replacements can't be read is left out: the model still sees why it can't be
|
|
9
10
|
* delivered.
|
|
10
11
|
*/
|
|
11
|
-
export async function loadReplacements(
|
|
12
|
+
export async function loadReplacements(context, cart, productIds = cart.items.map((item) => item.productId)) {
|
|
13
|
+
const { client, countryCode, onError } = context;
|
|
12
14
|
const unavailable = [
|
|
13
15
|
...new Set(cart.items
|
|
14
16
|
.filter((item) => item.isUnavailable && productIds.includes(item.productId))
|
|
@@ -17,17 +19,22 @@ export async function loadReplacements({ client, countryCode, onError }, cart, p
|
|
|
17
19
|
const entries = await Promise.all(unavailable.map(async (productId) => {
|
|
18
20
|
try {
|
|
19
21
|
const { replacements } = await client.cart.getReplacements(productId);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.map((tile) => formatProduct({ tile, boughtBefore: false }, countryCode))
|
|
24
|
-
|
|
22
|
+
const tiles = replacements.filter((tile) => !tile.isUnavailable).slice(0, MAX_REPLACEMENTS);
|
|
23
|
+
return {
|
|
24
|
+
productId,
|
|
25
|
+
products: tiles.map((tile) => formatProduct({ tile, boughtBefore: false }, countryCode)),
|
|
26
|
+
images: imageUrls(context, tiles.map((tile) => ({ productId: tile.id, imageId: tile.imageId }))),
|
|
27
|
+
};
|
|
25
28
|
}
|
|
26
29
|
catch (error) {
|
|
27
30
|
onError(error);
|
|
28
31
|
return null;
|
|
29
32
|
}
|
|
30
33
|
}));
|
|
31
|
-
|
|
34
|
+
const found = entries.filter((entry) => entry !== null);
|
|
35
|
+
return {
|
|
36
|
+
byProduct: new Map(found.map((entry) => [entry.productId, entry.products])),
|
|
37
|
+
images: Object.assign({}, ...found.map((entry) => entry.images)),
|
|
38
|
+
};
|
|
32
39
|
}
|
|
33
40
|
//# sourceMappingURL=replacements.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replacements.js","sourceRoot":"","sources":["../../src/tools/replacements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,aAAa,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"replacements.js","sourceRoot":"","sources":["../../src/tools/replacements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE1E,OAAO,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAG3D,2EAA2E;AAC3E,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,kDAAkD;AAClD,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAoB,EACpB,IAAU,EACV,UAAU,GAAsB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;IAExE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACjD,MAAM,WAAW,GAAG;QAClB,GAAG,IAAI,GAAG,CACR,IAAI,CAAC,KAAK;aACP,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;aAC3E,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CACjC;KACF,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IAEzB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;QAClC,IAAI,CAAC;YACH,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;YACtE,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAC5F,OAAO;gBACL,SAAS;gBACT,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,WAAW,CAAC,CAAC;gBACxF,MAAM,EAAE,SAAS,CACf,OAAO,EACP,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CACrE;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IACxD,OAAO;QACL,SAAS,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3E,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;KACjE,CAAC;AACJ,CAAC"}
|
package/dist/tools/result.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { CallToolResult } from "@modelcontextprotocol/server";
|
|
2
|
-
/**
|
|
3
|
-
export declare
|
|
2
|
+
/** The `_meta` key with the image URLs of the products in a result, by product id. */
|
|
3
|
+
export declare const IMAGES_META_KEY = "picnic-mcp/images";
|
|
4
|
+
/** Image URLs by product id. */
|
|
5
|
+
export type ImageUrls = Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Returns data as compact JSON text: models read the text, and indentation only costs tokens.
|
|
8
|
+
* Image URLs go in `_meta`, which clients keep from the model, so they cost no tokens.
|
|
9
|
+
*/
|
|
10
|
+
export declare function jsonResult(data: unknown, images?: ImageUrls): CallToolResult;
|
|
4
11
|
export declare function errorResult(message: string): CallToolResult;
|
|
5
12
|
/** Runs a tool handler and turns a thrown error into a short tool error. */
|
|
6
13
|
export declare function runTool(run: () => Promise<CallToolResult>, onError: (error: unknown) => void): Promise<CallToolResult>;
|
package/dist/tools/result.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { describeError } from "../errors.js";
|
|
2
|
-
/**
|
|
3
|
-
export
|
|
4
|
-
|
|
2
|
+
/** The `_meta` key with the image URLs of the products in a result, by product id. */
|
|
3
|
+
export const IMAGES_META_KEY = "picnic-mcp/images";
|
|
4
|
+
/**
|
|
5
|
+
* Returns data as compact JSON text: models read the text, and indentation only costs tokens.
|
|
6
|
+
* Image URLs go in `_meta`, which clients keep from the model, so they cost no tokens.
|
|
7
|
+
*/
|
|
8
|
+
export function jsonResult(data, images = {}) {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
11
|
+
...(Object.keys(images).length > 0 && { _meta: { [IMAGES_META_KEY]: images } }),
|
|
12
|
+
};
|
|
5
13
|
}
|
|
6
14
|
export function errorResult(message) {
|
|
7
15
|
return { content: [{ type: "text", text: message }], isError: true };
|
package/dist/tools/result.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/tools/result.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,
|
|
1
|
+
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/tools/result.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAKnD;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa,EAAE,MAAM,GAAc,EAAE;IAC9D,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACvE,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,GAAkC,EAClC,OAAiC;IAEjC,IAAI,CAAC;QACH,OAAO,MAAM,GAAG,EAAE,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,CAAC;QACf,OAAO,WAAW,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { formatProduct } from "../format/products.js";
|
|
3
3
|
import { listCandidates, matchProducts } from "../match.js";
|
|
4
|
-
import { SEARCH_LANGUAGES } from "./context.js";
|
|
4
|
+
import { SEARCH_LANGUAGES, imageUrls } from "./context.js";
|
|
5
5
|
import { jsonResult, runTool } from "./result.js";
|
|
6
6
|
/** At most this many products in the output, to keep it short. */
|
|
7
7
|
const MAX_PRODUCTS = 8;
|
|
@@ -24,29 +24,33 @@ export function registerSearchProducts(server, context) {
|
|
|
24
24
|
const corrected = results.correctedQuery && results.correctedQuery !== query
|
|
25
25
|
? { searchedFor: results.correctedQuery }
|
|
26
26
|
: {};
|
|
27
|
+
const images = (shown) => imageUrls(context, shown.map(({ tile }) => ({ productId: tile.id, imageId: tile.imageId })));
|
|
27
28
|
if (match.type === "best") {
|
|
29
|
+
const others = match.others.slice(0, MAX_PRODUCTS - 1);
|
|
28
30
|
return jsonResult({
|
|
29
31
|
...corrected,
|
|
30
32
|
next: "Add bestMatch with add_to_cart, unless the user asked for something else.",
|
|
31
33
|
bestMatch: formatProduct(match.best, context.countryCode),
|
|
32
|
-
otherResults: format(
|
|
33
|
-
});
|
|
34
|
+
otherResults: format(others),
|
|
35
|
+
}, images([match.best, ...others]));
|
|
34
36
|
}
|
|
35
37
|
if (match.type === "choose") {
|
|
38
|
+
const others = match.others.slice(0, MAX_PRODUCTS - match.choices.length);
|
|
36
39
|
return jsonResult({
|
|
37
40
|
...corrected,
|
|
38
41
|
next: "No clear match. Ask the user which of the choices they want, then add it with add_to_cart.",
|
|
39
42
|
choices: format(match.choices),
|
|
40
|
-
otherResults: format(
|
|
41
|
-
});
|
|
43
|
+
otherResults: format(others),
|
|
44
|
+
}, images([...match.choices, ...others]));
|
|
42
45
|
}
|
|
46
|
+
const others = match.others.slice(0, MAX_PRODUCTS);
|
|
43
47
|
return jsonResult({
|
|
44
48
|
...corrected,
|
|
45
|
-
next:
|
|
49
|
+
next: others.length > 0
|
|
46
50
|
? "None of the results can be ordered right now. Tell the user, or try another search term."
|
|
47
51
|
: "Nothing found. Try another search term, or tell the user.",
|
|
48
|
-
otherResults: format(
|
|
49
|
-
});
|
|
52
|
+
otherResults: format(others),
|
|
53
|
+
}, images(others));
|
|
50
54
|
}, context.onError));
|
|
51
55
|
}
|
|
52
56
|
//# sourceMappingURL=search-products.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-products.js","sourceRoot":"","sources":["../../src/tools/search-products.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAoB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"search-products.js","sourceRoot":"","sources":["../../src/tools/search-products.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAoB,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElD,kEAAkE;AAClE,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,MAAM,UAAU,sBAAsB,CAAC,MAAiB,EAAE,OAAoB;IAC5E,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACvD,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,8EAA8E;YAC9E,sBAAsB,QAAQ,6BAA6B;YAC3D,sGAAsG;YACtG,gKAAgK;QAClK,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,iCAAiC,QAAQ,GAAG,CAAC;SACvF,CAAC;QACF,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KACzD,EACD,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,aAAa,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,CAAC,UAAuB,EAAE,EAAE,CACzC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;QAC/E,MAAM,SAAS,GACb,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,cAAc,KAAK,KAAK;YACxD,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,cAAc,EAAE;YACzC,CAAC,CAAC,EAAE,CAAC;QAET,MAAM,MAAM,GAAG,CAAC,KAAkB,EAAE,EAAE,CACpC,SAAS,CACP,OAAO,EACP,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CACzE,CAAC;QAEJ,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;YACvD,OAAO,UAAU,CACf;gBACE,GAAG,SAAS;gBACZ,IAAI,EAAE,2EAA2E;gBACjF,SAAS,EAAE,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC;gBACzD,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC;aAC7B,EACD,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,CAChC,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC1E,OAAO,UAAU,CACf;gBACE,GAAG,SAAS;gBACZ,IAAI,EAAE,4FAA4F;gBAClG,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;gBAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC;aAC7B,EACD,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,CACtC,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;QACnD,OAAO,UAAU,CACf;YACE,GAAG,SAAS;YACZ,IAAI,EACF,MAAM,CAAC,MAAM,GAAG,CAAC;gBACf,CAAC,CAAC,0FAA0F;gBAC5F,CAAC,CAAC,2DAA2D;YACjE,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC;SAC7B,EACD,MAAM,CAAC,MAAM,CAAC,CACf,CAAC;IACJ,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CACtB,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "picnic-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for the online supermarket Picnic: search products and manage the cart",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"picnic",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
50
|
-
"picnic-api": "^5.
|
|
50
|
+
"picnic-api": "^5.2.0",
|
|
51
51
|
"zod": "^4.6.7"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|