Skip to main content
Glama
nicktcode

Swissgroceries MCP

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

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch term in any language, e.g. "Milch", "pâtes", "Bier". At least 1 character.
chainsNoRestrict search to specific chains. Omit to search all configured chains in parallel.
storeIdsNoFilter results to products available in these store IDs (chain-specific internal IDs).
filtersNoOptional product filters applied after search.
limitNoMaximum number of results per chain (1–50). Defaults to chain-specific limit.
offsetNoSkip the first N results per chain. Use with `limit` to paginate. Default 0.

Implementation Reference

  • 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 };
    }
  • 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.');
  • 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,
    },
  • 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';
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must cover behavioral details. It mentions results are grouped by chain and include price, unit price, size, and promotion info, but does not explain pagination (limit/offset defaults), rate limits, or auth requirements. The parallel search across chains is also not disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences: one for main action, one for filters, one for output and examples. Highly concise, front-loaded, and every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers purpose, filters, and output format well, but lacks details on default pagination, result ordering, and treatment of empty results. Given the complexity (nested filters, no output schema), it is mostly complete but has minor gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description adds value by listing output fields (normalized price, unit price, size, promotion info) and providing concrete examples that tie parameters to use cases, which helps the agent understand parameter combinations.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches for products across specific Swiss grocery chains by keyword, with filters and grouped results. It provides concrete example queries, effectively distinguishing it from siblings like get_product (single product) or find_stock (stock levels).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives specific use cases (e.g., 'find organic milk under 2 CHF') and implies usage for multi-chain searches. However, it does not explicitly state when to use other tools like get_product or find_stock, leaving some ambiguity for the agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nicktcode/swissgroceries-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server