age:AI Sign in

Dev Kick zero-shot prompt: Bulk Barn MCP

Paste it into Devin (SWE 1.7 Lightning) and press send.

devkick presents age:AI Bulk Barn MCP zero-shot speedrun

Goal

You are starting from an empty directory. Build the entire BulkBarnMCP project from scratch, install it, stand it up, and write the HTML instructions so it can be demonstrated live. The MCP name must be BulkBarnMCP (FastMCP("BulkBarnMCP")).

When this prompt finishes, ALL of the following must be true:

  1. Python dependencies are installed from requirements.txt.
  2. The mcp-server/server.py file exists and implements the full tool set described below.
  3. mcp-server/densities.json and mcp-server/aliases.json exist and are loaded by the server. These must be built from scratch, not copied from a pre-filled list.
  4. A Bulk Barn product catalog exists under data/ and was built by scraping the Bulk Barn category URLs you discover.
  5. mcp-server/test_server.py and mcp-server/test_mcp_client.py exist and pass against the catalog you built.
  6. build-catalog.py exists as a cross-platform Python scraper.
  7. site/index.html and site/guide.html exist. index.html links to guide.html; guide.html contains the connection instructions and tool list.
  8. README.md and AGENTS.md exist with project documentation and agent notes.
  9. .gitignore exists.
  10. The MCP server is running in SSE mode on port 8000.
  11. The static site is being served on port 8080 and guide.html is reachable.
  12. Smoke tests pass, the SSE endpoint responds, and the guide page displays correctly in a browser.

Environment assumptions

  • Windows with PowerShell 7 or macOS with bash/zsh.
  • Python 3.11+ is installed. On Windows use python; on macOS use python3 if python is not available.
  • pip (or pip3 on macOS) can install from requirements.txt.
  • Internet access is available.

Project layout

Create this directory tree in the project root:

<project-root>
  data/                          # one JSON file per category + _index.json
  mcp-server/
    server.py                    # FastMCP server named BulkBarnMCP
    densities.json               # unit + density table (built from web sources)
    aliases.json                 # ingredient aliases / not-available flags (built from scraped products)
    test_server.py               # module smoke test
    test_mcp_client.py           # stdio MCP client smoke test
  site/
    index.html                   # landing page with link to guide
    guide.html                   # connection guide
  build-catalog.py               # Python scraper for the Bulk Barn catalog
  requirements.txt
  README.md
  AGENTS.md
  .gitignore

Step 0 - Create directory structure

Use a Python one-liner so it works on both Windows and macOS:

python -c "import os; [os.makedirs(d, exist_ok=True) for d in ('mcp-server', 'site', 'data')]"

On macOS, if python is not available, use python3.

Step 1 - requirements.txt

Create requirements.txt with these dependencies:

mcp>=1.0.0
thefuzz>=0.22.1
python-Levenshtein>=0.27.4

Add requests and beautifulsoup4 if you use the HTTP scraper, or playwright / selenium if you use a browser-based approach.

Step 2 - .gitignore

Create .gitignore ignoring Python build artifacts, virtual environments, the nutrition cache, and IDE folders:

# Python
__pycache__/
*.pyc
*.pyo
*.egg-info/
.venv/

# Generated / cached data
nutrition-cache.json

# IDE
.vscode/
.idea/

Step 3 - Scrape the Bulk Barn catalog

The catalog is a directory data/ with one JSON file per category plus _index.json.

Discover the categories

Start by fetching the category list from:

https://www.bulkbarn.ca/cmspages/bulkbarn/get_categories.aspx?lang=en

Parse the XML to discover every category. Each category has an id, a name, and a URL path. Build the category page URL from the path (prepend https://www.bulkbarn.ca/en/ to the path as needed).

Exclude non-food categories like:

  • Cleaning and Personal Care
  • Cake Decorating & Candy Making
  • Glassware & Gadgets
  • Health
  • Petfood & Birdseed
  • Pinatas
  • Vitamins & Supplements

Scrape the remaining food categories.

What to extract

For each category page, extract every product and build objects with:

  • code - the bin code / PLU (a numeric string, typically 1 to 7 digits).
  • name - the product name, HTML-decoded and trimmed.
  • product_url - the absolute product detail URL. If the href is relative, prepend https://www.bulkbarn.ca.

Deduplicate by code and sort products by code ascending within each category file.

Per-category file schema

Each per-category JSON file:

{
  "category": "Category Name",
  "catid": 430,
  "source_url": "https://www.bulkbarn.ca/en/Products/Categories/Category-Slug",
  "scraped_at": "2026-08-14T22:57:49+00:00",
  "count": 123,
  "products": [
    {
      "code": "<bin code>",
      "name": "<product name>",
      "product_url": "https://www.bulkbarn.ca/en/Products/All/<slug>-<code>"
    }
  ]
}

_index.json schema

{
  "source": "https://www.bulkbarn.ca/en/Products",
  "generated_at": "2026-08-14T22:57:49+00:00",
  "categories": [
    { "catid": 430, "name": "Category Name", "file": "category-file-stem.json", "count": 123 }
  ],
  "total": 123
}

How to scrape

The simplest approach is a plain HTTP GET of each category page. Use a realistic User-Agent header. The product grid is present in the returned HTML, inside <li class="prod-thumbnail"> elements. Each thumbnail contains an <a> element with:

  • data-prod-bbplu - the bin code / PLU.
  • data-prod-name - the product name (HTML-encoded).
  • href - the product detail URL, typically relative (e.g., /en/Products/All/<slug>-<code>).

Parse the HTML, extract those fields for every thumbnail, HTML-decode the name, build absolute product URLs, deduplicate by code, and sort.

If a category page uses pagination or lazy/infinite scroll, discover and fetch the additional pages. Inspect the page for pagination links or XHR endpoints and replicate the requests.

If the grid does not appear in a plain GET, you may also use Playwright, Selenium, or manual network capture to retrieve the rendered HTML or the underlying product API calls. Do not rely on any machine-specific credential store or paid scraping service.

Write the scraper as build-catalog.py. It must accept --out-dir, --force, and optionally --category-id / --include-category-ids arguments. If a category returns zero products, retry once with a longer delay.

Step 4 - mcp-server/densities.json

Create mcp-server/densities.json from scratch. Do not copy a pre-made table. Research standard culinary densities and build the file.

units

Each key is a unit string. Each value has:

  • type: weight, volume, or count.
  • For weight: grams per unit.
  • For volume: ml per unit.
  • For count: no extra fields.

Include common weight, volume, and count units. Use standard metric and US customary conversion values.

ingredients

Each ingredient object has:

  • patterns: list of strings to match against a recipe ingredient name.
  • density_g_per_cup: float grams per US cup.

Build this from web sources. Search for "grams per cup" for the ingredients you find in the Bulk Barn catalog. Cover the major categories found in your scraped data: flours, sugars, fats, nuts, seeds, dried fruit, grains, beans, pasta, spices, baking supplies, sweeteners, spreads, and dairy products.

If a density cannot be found, omit the ingredient from the density table. The server will then report that it cannot determine the purchase weight.

Step 5 - mcp-server/aliases.json

Create mcp-server/aliases.json from scratch. Do not copy a pre-made list.

The aliases object maps a normalized recipe ingredient name to a bin code and optional note:

{
  "aliases": {
    "<recipe term>": { "code": "<bin code or null>", "note": "<optional note>" }
  }
}

Build it by inspecting the products you scraped. For common ambiguous recipe terms (salt, sugar, brown sugar, flour, baking powder, walnuts, oats, rice, cinnamon, honey, peanut butter, etc.), choose the most representative Bulk Barn product.

For items Bulk Barn does not sell or only sells in a form that is not what a typical recipe means (fresh butter, eggs, dairy milk, olive oil, water), set code to null and add a helpful note.

Step 6 - mcp-server/server.py

Create mcp-server/server.py. It must be a FastMCP server named BulkBarnMCP and expose exactly these tools:

  • list_categories() -> str (JSON array of categories from _index.json).
  • search_products(query: str, category: Optional[str] = None, limit: int = 10) -> str (JSON array of matching products with match_score).
  • get_product(code: str) -> str (JSON product or error).
  • get_nutrition(code: str, force_refresh: bool = False) -> str (JSON nutrition payload or error).
  • get_recipe_nutrition(ingredients: str, force_refresh: bool = False) -> str (JSON with ingredients array and totals).
  • convert_recipe_to_shopping_list(ingredients: str) -> str (JSON array of matched recipe items).
  • list_densities() -> str (JSON of densities table).

Server behavior

  1. Load _index.json from data/_index.json. For each category, load its file and build:

    • _catalog: list of all products as {code, name, category, catid, product_url?}.
    • _products_by_code: dict mapping code to product.
    • _index: the parsed index.
  2. Load densities.json and aliases.json into _densities and _aliases.

  3. Ingredient parsing:

    • Normalize text: lowercase, NFKC, remove parentheses, collapse whitespace.
    • Replace Unicode fractions with decimal equivalents.
    • Strip common prep and size words.
    • Parse quantities: mixed numbers, simple fractions, decimals, integers. Handle ranges by taking the first quantity.
    • Handle qualitative amounts: pinch, dash, to taste.
    • Return a dict with raw, quantity, unit, name, original_name.
  4. Product matching:

    • Check aliases first. If an alias has a code and it exists in the catalog, return that product with match_score 100 and optional alias_note.
    • If no alias, use thefuzz.process.extractOne with a custom scorer that tokenizes and stems ingredient and product names, rewards full query token overlap, and penalizes extra product words. Match threshold is 50.
    • For search_products, also use process.extract with the same scorer, respecting limit and optional category filter.
  5. Weight calculation:

    • Look up unit in _densities["units"].
    • If unit is weight, multiply quantity by grams.
    • If unit is volume, look up ingredient density (_find_density). If found, compute quantity * ml_per_unit * density / 236.588.
    • If unit is count, return None and add a note that the item is sold by count.
    • Round purchase weight: >= 1000 g -> kg with 2 decimals; >= 1 g -> ceil to integer grams; else 2 decimals.
  6. convert_recipe_to_shopping_list:

    • Split ingredients by lines.
    • For each line, parse it, find product, calculate grams, build alternatives list (top 3 minus the matched product, limit to 2), and return a JSON array.
    • Each result includes: ingredient, parsed (quantity, unit, ingredient_name), product, code, category, purchase_grams, purchase_weight, match_score, alternatives, and optional notes.
    • If an alias has code: null, return status not_available with the alias note.
    • If no product match, return status no_match.
    • Notes to include: alias note, weight-unknown note, low-confidence note (match score < 75).
  7. Nutrition fetching (get_nutrition):

    • Maintain a JSON cache in nutrition-cache.json at the project root.
    • If force_refresh is false and code is cached, return cached value.
    • Use urllib.request with a browser User-Agent to fetch product_url.
    • Parse the HTML: split on "Nutrition Facts", take the last section, strip HTML tags and * characters, collapse whitespace.
    • Extract: serving_size (prefer parenthesized (X g|ml), else first X g|ml), calories, fat_g, saturated_g, trans_g, cholesterol_mg, sodium_mg, carbohydrate_g, fibre_g, sugars_g, sugar_alcohol_g, protein_g, vitamin_a_percent, vitamin_c_percent, calcium_percent, iron_percent, potassium_mg.
    • Return a payload with code, product, product_url, fetched_at (ISO 8601 UTC), and nutrition.
    • On error, return {"error": ...}.
  8. get_recipe_nutrition:

    • Call convert_recipe_to_shopping_list, parse JSON.
    • For each matched ingredient with purchase_grams, call get_nutrition (respecting force_refresh).
    • Scale nutrition to the purchased grams using the serving size. Parse serving size as amount unit (g or ml). Use scale = grams / serving_amount. For ml, approximate 1 ml = 1 g.
    • Sum numeric fields into totals (exclude serving_size_used, scaled_to_grams).
    • Return {"ingredients": [...], "totals": {...}}.
  9. CLI:

    • argparse with --transport (stdio or sse, default stdio) and --port (default 8000).
    • When --transport sse, set mcp.settings.port = args.port then call mcp.run(transport="sse").
    • When stdio, call mcp.run().
    • Load catalog, densities, and aliases before starting.

Implementation notes

  • Use from mcp.server.fastmcp import FastMCP and thefuzz.
  • Paths: derive project root from Path(__file__).parent.parent. Data dir is PROJECT_ROOT / "data".
  • Density file is Path(__file__).parent / "densities.json".
  • Aliases file is Path(__file__).parent / "aliases.json".
  • Nutrition cache is PROJECT_ROOT / "nutrition-cache.json".
  • All tool outputs are JSON strings.
  • Do not pass port to mcp.run(). Use mcp.settings.port.

Step 7 - mcp-server/test_server.py

Create mcp-server/test_server.py that:

  1. Adds mcp-server/ to sys.path.
  2. Imports server.
  3. Calls server.load_catalog(), server.load_densities(), server.load_aliases().
  4. Prints catalog size: <len(_catalog)>.
  5. Prints --- search 'salt' --- and server.search_products("salt", limit=5). If "salt" returns nothing, search for another common term from your catalog.
  6. Prints --- search 'flour' --- and server.search_products("flour", limit=3) (or another common term).
  7. Prints --- recipe --- and server.convert_recipe_to_shopping_list(...) with this recipe:
    • 2 cups all-purpose flour
    • 100 g walnuts
    • 1 tsp salt
    • 1/2 cup brown sugar
    • 1 tsp baking powder
    • 3 tbsp olive oil
    • 2 tbsp butter
  8. Prints --- nutrition for a known product ---. Pick the first product returned by search_products("flour", limit=1), extract its code, and call server.get_nutrition(code)[:1200].

The script exits 0 on success.

Step 8 - mcp-server/test_mcp_client.py

Create mcp-server/test_mcp_client.py that:

  1. Uses mcp.ClientSession and mcp.client.stdio.stdio_client.
  2. Starts the server with StdioServerParameters(command=sys.executable, args=[str(SERVER)]).
  3. Lists tools and prints tool names.
  4. Calls convert_recipe_to_shopping_list with ingredients 2 cups all-purpose flour\n1 tsp salt\n100 g walnuts and prints the result.
  5. Calls get_nutrition with the code of the first flour product returned by the previous call, and prints the first 1000 characters.
  6. Calls get_recipe_nutrition with the same ingredients plus 1/2 cup brown sugar and prints the first 1000 characters.

The script exits 0 on success.

Step 9 - site/guide.html

Create site/guide.html as a clean, responsive HTML page. Requirements:

  • Use Bulk Barn brand colors: #7e2d26 (dark red) and #a8453c (lighter red) for headings; a warm off-white background #f7f3ee and white cards with a light border.
  • Header: "Bulk Barn MCP" and "Connection guide for the live demonstration".
  • Sections:
    1. What is running - short description and endpoint badges for stdio and SSE, with http://localhost:8000/sse and http://localhost:8000/messages.
    2. Available MCP tools - list all seven tools with their signatures.
    3. Connect Claude Desktop (stdio) - show both config paths:
      • Windows: %APPDATA%\Claude\claude_desktop_config.json
      • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Include a JSON block with mcpServers.bulkbarn pointing to the absolute path of mcp-server/server.py and tell the user to adjust the path and command (python on Windows, python3 on macOS).
    4. Connect Claude Code (stdio) - show the claude config add mcpServers ... command.
    5. Connect an SSE client - show python mcp-server/server.py --transport sse --port 8000 (use python3 on macOS) and http://localhost:8000/sse.
    6. ChatGPT / GPTs - note that ChatGPT does not natively connect to local MCP servers; use Claude Desktop, Claude Code, or an SSE client.
    7. Try a live prompt - include the example recipe and the example What are the nutrition facts for Bulk Barn code <code>? prompt. Use a real code from your catalog (e.g., the code for all-purpose flour or table salt).
    8. Health checks - list python mcp-server/test_server.py (or python3 on macOS), python mcp-server/test_mcp_client.py (or python3 on macOS), and curl http://localhost:8000/sse.
  • Include a "Back to landing page" link to index.html.
  • Footer: "Bulk Barn MCP demonstration - generated by Devin".
  • Style should be mobile-friendly.

Step 10 - site/index.html

Create site/index.html as a simple marketing landing page. Requirements:

  • Use Bulk Barn brand red #e12a28 and white/neutral tones.
  • A top menu bar with the project name/logo and a "Connect guide" link to guide.html.
  • Hero headline: "bake with bulk barn MCP" (or similar), tagline about converting recipes to Bulk Barn shopping lists.
  • Feature list with checkmarks:
    • Recipe to Bulk Barn shopping list conversion
    • Smart ingredient matching with curated aliases
    • Auto weight conversion from cups, spoons, and counts
    • Per-product nutrition facts pulled from bulkbarn.ca
    • Full recipe nutrition totals at a glance
    • Search the catalog by name or category
    • Works inside Claude Desktop as an MCP server
  • A "Copy Claude Desktop config" button that copies a JSON config block to the clipboard using navigator.clipboard.writeText. Include microcopy "Paste it into your Claude Desktop config file" and show both Windows and macOS paths below the button.
  • A link "View full connection guide" to guide.html.
  • Mobile-friendly.
  • Do not depend on external video or image assets. If you want media, use CSS-only design or data URIs. Keep the page self-contained.

Step 11 - README.md

Create README.md with:

  • Title "# Bulk Barn MCP".
  • One-paragraph description of what the server does.
  • "What's included" section listing data/, mcp-server/server.py, mcp-server/densities.json, mcp-server/aliases.json, build-catalog.py, nutrition-cache.json.
  • Prerequisites (Python 3.11+, Windows or macOS, internet access for the HTTP scraper).
  • Install command with python -m pip install -r requirements.txt (use python3 -m pip on macOS if needed).
  • Refresh catalog command python build-catalog.py --force (use python3 on macOS).
  • Run stdio and SSE server commands.
  • Claude Desktop config block with the correct absolute path for the current machine.
  • Available tools list.
  • Example recipe prompt.
  • Data accuracy and nutrition caching notes.

Step 12 - AGENTS.md

Create AGENTS.md with:

  • Project layout.
  • Common commands (install, refresh catalog, module smoke test, stdio smoke test, syntax check).
  • Claude Desktop config block.
  • Notes about the HTTP scraper, nutrition cache, and purchase-weight estimates.
  • Landing page notes.

Step 13 - Install and test

Run these commands from the project root, using python3 on macOS where needed:

python -m pip install -r requirements.txt
python -m py_compile mcp-server/server.py
python mcp-server/test_server.py
python mcp-server/test_mcp_client.py

All must complete with exit code 0.

If the catalog is missing, first run the catalog build before testing.

Step 14 - Start services

From one terminal session, start the MCP server in SSE mode:

python mcp-server/server.py --transport sse --port 8000

From a second session, start the static site server:

python -m http.server 8080 --directory site

Use python3 on macOS if python is not available.

Step 15 - Verify

Run these checks and confirm output:

# SSE endpoint should stream an event: endpoint
curl -N http://localhost:8000/sse

# Guide page should return HTML
curl http://localhost:8080/guide.html

For a port check, use the appropriate command:

  • Windows PowerShell: Test-NetConnection -ComputerName localhost -Port 8000 and Test-NetConnection -ComputerName localhost -Port 8080. Both TcpTestSucceeded should be True.
  • macOS: nc -zv localhost 8000 and nc -zv localhost 8080. Both should report success.

Definition of done

Before declaring complete, confirm ALL of these:

  1. requirements.txt, mcp-server/server.py, mcp-server/densities.json, mcp-server/aliases.json, mcp-server/test_server.py, mcp-server/test_mcp_client.py, build-catalog.py, site/index.html, site/guide.html, README.md, AGENTS.md, and .gitignore all exist.
  2. FastMCP is instantiated with the name "BulkBarnMCP".
  3. python -m pip install -r requirements.txt (or python3 -m pip) completed successfully.
  4. python mcp-server/test_server.py (or python3) exits 0.
  5. python mcp-server/test_mcp_client.py (or python3) exits 0.
  6. http://localhost:8000/sse returns a Server-Sent Events stream.
  7. http://localhost:8080/guide.html returns the connection guide HTML.
  8. site/index.html links to guide.html.
  9. The guide lists the MCP tools, endpoint URLs, and Claude Desktop config.
  10. densities.json and aliases.json were built from scratch, not pasted from a pre-filled list.

Common pitfalls

  • Do not pass port to mcp.run(). The correct SSE pattern is:
    mcp.settings.port = args.port
    mcp.run(transport="sse")
  • Use Path(__file__).parent.parent for the project root, not a hard-coded string.
  • Ensure data/_index.json exists and its categories array references files that exist.
  • Nutrition fetching requires product_url in the catalog.
  • The search_products and convert_recipe_to_shopping_list tools must return JSON strings, not Python objects.
  • Keep all tool names in snake_case.
  • Do not use em dashes in documentation; use hyphens.
  • The category list endpoint returns XML; the category pages return HTML with the product grid. Parse both correctly.
  • Use a realistic User-Agent when making HTTP requests; some endpoints may block default library headers.

Open this in the age:AI app

The prompt is in the age:AI app. Install it, open it and tap Dev Kick.

Get it on Google Play

On an iPhone, open embrace.ageai.io in Safari, choose Add to Home Screen from the Share menu, and open age:AI from your home screen.