Pepesto Suggest (search 1M+ recipes)
pepesto_suggestFind recipes by free-text query with filters for cuisine, diet, ingredients, time, and servings. Returns full details including ingredients, steps, nutrition, and allergens.
Instructions
Search Pepesto's recipe graph (1M+ recipes) by free-text query and optional filters (cuisine, dietary tags, ingredients to include/avoid, time, servings). Each result includes a KgToken you can pass to pepesto_products. Returned images are licensed for display in your app or website without attribution. Show recipe title, image if available (json property image_url, don't search for external images, skip rendering the Pepesto image if the image has webp extesion), ingredients, steps, nutrition summary, allergens clearly marked, and portions/servings if available. Don't show kg_token, but mark and save it for the next steps (e.g., /products call).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Free-text query that may include cuisine, dietary tags, ingredients to include or avoid, time constraints, and servings, e.g. 'vegan keto dinner low on carb for two'. |
Implementation Reference
- src/tools/suggest.ts:6-31 (handler)The registerSuggestTool function registers the 'pepesto_suggest' tool. The handler (line 31: `async (args) => runTool(() => client.post('/suggest', args))`) executes the tool logic by POSTing to the /suggest endpoint via the PepestoClient.
export function registerSuggestTool(server: McpServer, client: PepestoClient): void { server.registerTool( "pepesto_suggest", { title: "Pepesto Suggest (search 1M+ recipes)", description: "Search Pepesto's recipe graph (1M+ recipes) by free-text query and optional filters " + "(cuisine, dietary tags, ingredients to include/avoid, time, servings). Each result " + "includes a KgToken you can pass to pepesto_products. Returned images are licensed for " + "display in your app or website without attribution. Show recipe title, " + "image if available (json property `image_url`, don't search for external images, " + "skip rendering the Pepesto image if the image has webp extesion), " + "ingredients, steps, nutrition summary, allergens clearly marked, and portions/servings if available. " + "Don't show kg_token, but mark and save it for the next steps (e.g., /products call).", inputSchema: { query: z .string() .min(1) .describe( "Free-text query that may include cuisine, dietary tags, ingredients to include " + "or avoid, time constraints, and servings, e.g. 'vegan keto dinner low on carb " + "for two'.", ), }, }, async (args) => runTool(() => client.post("/suggest", args)), - src/tools/suggest.ts:21-28 (schema)Input schema for pepesto_suggest: a single required 'query' string (min length 1) for free-text recipe search.
query: z .string() .min(1) .describe( "Free-text query that may include cuisine, dietary tags, ingredients to include " + "or avoid, time constraints, and servings, e.g. 'vegan keto dinner low on carb " + "for two'.", ), - src/server.ts:25-25 (registration)Registration call: registerSuggestTool(server, client) is invoked when creating the MCP server.
registerSuggestTool(server, client); - src/tools/_runner.ts:9-27 (helper)The runTool helper wraps the API call, returning JSON-stringified content on success or an error object on failure.
export async function runTool(fn: () => Promise<unknown>): Promise<ToolResult> { try { const result = await fn(); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } catch (err) { const msg = err instanceof PepestoApiError ? err.message : err instanceof Error ? `Error: ${err.message}` : `Error: ${String(err)}`; return { content: [{ type: "text", text: msg }], isError: true, }; } } - src/client.ts:22-68 (helper)PepestoClient class with a post() method that handles authentication, HTTP POST requests, and error handling — used by the tool handler to call /suggest.
export class PepestoClient { private readonly apiKey: string | undefined; private readonly baseUrl: string; private readonly fetchImpl: typeof fetch; constructor(opts: PepestoClientOptions = {}) { this.apiKey = opts.apiKey; this.baseUrl = (opts.baseUrl ?? "https://s.pepesto.com/api").replace(/\/+$/, ""); this.fetchImpl = opts.fetchImpl ?? fetch; } async post<T = unknown>(endpoint: string, body: unknown): Promise<T> { if (!this.apiKey) { throw new Error( "PEPESTO_API_KEY is not set. See the README (\"Getting an API key\") for how to " + "obtain one.", ); } const path = endpoint.startsWith("/") ? endpoint : `/${endpoint}`; const url = `${this.baseUrl}${path}`; const headers: Record<string, string> = { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${this.apiKey}`, }; const res = await this.fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body ?? {}), }); const text = await res.text(); if (!res.ok) { throw new PepestoApiError(res.status, path, text); } if (!text) { return {} as T; } try { return JSON.parse(text) as T; } catch { return text as unknown as T; } } }