search_products
Search for products across Swiss grocery chains by keyword, with optional filters for price, size, and tags like organic or vegan. Returns results grouped by chain with normalized prices and promotions.
Instructions
Search for products across configured Swiss grocery chains (Migros, Coop, Aldi, Denner, Lidl) by keyword. Supports optional filters for price, size range, and product tags (organic, vegan, budget, etc.). Returns results grouped by chain with normalised price, unit price, size, and promotion info. Use for "find organic milk under 2 CHF", "compare pasta prices", or "search for gluten-free bread".
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search term in any language, e.g. "Milch", "pâtes", "Bier". At least 1 character. | |
| chains | No | Restrict search to specific chains. Omit to search all configured chains in parallel. | |
| storeIds | No | Filter results to products available in these store IDs (chain-specific internal IDs). | |
| filters | No | Optional product filters applied after search. | |
| limit | No | Maximum number of results per chain (1–50). Defaults to chain-specific limit. | |
| offset | No | Skip the first N results per chain. Use with `limit` to paginate. Default 0. |
Implementation Reference
- src/tools/search_products.ts:46-74 (handler)The main handler function that executes the search_products tool logic. It iterates over adapters with 'productSearch' capability, calls searchProducts on each, collects results grouped by chain, and returns them along with any errors.
export async function searchProductsHandler( registry: AdapterRegistry, input: SearchProductsInput, ): Promise<SearchProductsOutput> { const adapters = registry.withCapability('productSearch', input.chains); const errors: SearchProductsOutput['errors'] = []; const byChain: SearchProductsOutput['byChain'] = {}; await Promise.all( adapters.map(async (a) => { const r = await a.searchProducts({ query: input.query, storeIds: input.storeIds, tags: input.filters?.tags, maxPrice: input.filters?.maxPrice, sizeRange: input.filters?.sizeRange, limit: input.limit, offset: input.offset, }); if (r.ok) { byChain[a.chain] = r.data; } else { errors.push({ chain: a.chain, code: r.error.code, reason: 'reason' in r.error ? r.error.reason : undefined }); } }), ); return { byChain, errors: errors.length ? errors : undefined }; } - src/tools/search_products.ts:10-37 (schema)Zod schema defining the input parameters for search_products: query (string, required), chains (optional array of chain names), storeIds (optional), filters (optional with tags, maxPrice, sizeRange), limit (1-50), and offset (0-500).
export const searchProductsSchema = z.object({ query: z.string().min(1) .describe('Search term in any language, e.g. "Milch", "pâtes", "Bier". At least 1 character.'), chains: z.array(z.enum(['migros', 'coop', 'aldi', 'denner', 'lidl', 'farmy', 'volgshop', 'ottos'])) .optional() .describe('Restrict search to specific chains. Omit to search all configured chains in parallel.'), storeIds: z.array(z.string()) .optional() .describe('Filter results to products available in these store IDs (chain-specific internal IDs).'), filters: z.object({ tags: z.array(z.enum(TAG_VALUES)) .optional() .describe('Product tags to filter by, e.g. ["organic", "vegan"]. All tags must match.'), maxPrice: z.number().positive() .optional() .describe('Maximum product price in CHF (inclusive), e.g. 3.5.'), sizeRange: z.object({ minMl: z.number().nonnegative().optional().describe('Minimum size in millilitres (ml), e.g. 500.'), maxMl: z.number().nonnegative().optional().describe('Maximum size in millilitres (ml), e.g. 1500.'), }).optional().describe('Size range filter in millilitres; useful for beverages and liquids.'), }).optional().describe('Optional product filters applied after search.'), limit: z.number().int().positive().max(50) .optional() .describe('Maximum number of results per chain (1–50). Defaults to chain-specific limit.'), offset: z.number().int().nonnegative().max(500) .optional() .describe('Skip the first N results per chain. Use with `limit` to paginate. Default 0.'), }).describe('Search for products across configured Swiss grocery chains by keyword, with optional price, size, and tag filters. Returns results grouped by chain.'); - src/tools/search_products.ts:41-44 (schema)Output type definition for search_products: returns results grouped by chain (byChain) and optional errors array per chain.
export interface SearchProductsOutput { byChain: Partial<Record<Chain, NormalizedProduct[]>>; errors?: Array<{ chain: Chain; code: string; reason?: string }>; } - src/index.ts:61-71 (registration)Registration of the 'search_products' tool in the TOOLS array, mapping the name to its schema and handler, with a description of its functionality.
{ name: 'search_products', description: [ 'Search for products across configured Swiss grocery chains (Migros, Coop, Aldi, Denner, Lidl) by keyword.', 'Supports optional filters for price, size range, and product tags (organic, vegan, budget, etc.).', 'Returns results grouped by chain with normalised price, unit price, size, and promotion info.', 'Use for "find organic milk under 2 CHF", "compare pasta prices", or "search for gluten-free bread".', ].join(' '), schema: searchProductsSchema, handler: searchProductsHandler, }, - src/adapters/types.ts:188-196 (helper)SearchQuery interface used by all chain adapters' searchProducts method, defining query, storeIds, tags, maxPrice, sizeRange, limit, and offset fields.
export interface SearchQuery { query: string; storeIds?: string[]; tags?: Tag[]; maxPrice?: number; sizeRange?: { minMl?: number; maxMl?: number }; limit?: number; offset?: number; language?: 'de' | 'fr' | 'it' | 'en';