Loewen Menu Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Loewen Menu Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
MCP (Model Context Protocol) server for the Löwen Menü IBS5 school-lunch ordering system. Lets an LLM browse weekly menus, manage a shopping cart, and place meal orders via the IBS5 web portal at ibs.loewen-menue.de.
get_menu)plan_order)bulk_add_to_cart), to the cart# Clone and install
git clone https://github.com/yourusername/loewen-menu-mcp.git
cd loewen-menu-mcp
uv syncCredentials are passed via the PROFILES environment variable:
PROFILES=child1:CUSTOMER_ID:PASSWORDFor multiple children:
PROFILES=child1:S000001:1234,child2:S000002:5678If only one profile is configured, it is used as the default and the profile parameter can be omitted from all tool calls.
Add to your Claude Desktop MCP config (~/.claude/settings.json or a project-level MCP file):
{
"mcpServers": {
"loewen-menu": {
"command": "/bin/bash",
"args": ["-c", "cd /path/to/loewen-menu-mcp && uv run server.py"],
"env": {
"PROFILES": "child1:S000001:1234,child2:S000002:5678"
}
}
}
}| Tool | Description |
|---|---|
list_profiles | List configured profiles with the real account holder name, warning on any label/holder mismatch |
get_weekly_menu | Browse meals for a given calendar week |
get_menu | Browse meals for an arbitrary date range, with dietary tags per meal (markdown or JSON) |
plan_order | Dry run — preview total cost + per-line conflicts for a multi-meal order, without mutating |
get_order_history | View past orders (filterable by days back and search text) |
get_cart_status | Check shopping cart contents, balance, and projected balance after the cart |
add_to_cart | Add a single meal to the cart (does not finalize the order) |
bulk_add_to_cart | Add many meals to the cart at once; per-item validation, skip+report on failure |
confirm_order | Finalize and submit all items in the cart |
remove_meal | Stage a cart quantity of 0 (does not cancel a confirmed order on its own) |
cancel_order | Cancel confirmed orders: stage + confirm + verify the offsetting -1 rows |
get_menu (or get_weekly_menu) — see what's available, with dietary tagsplan_order — dry-run the meals you intend to order; check order_total and conflictsbulk_add_to_cart (or add_to_cart) — add the mealsget_cart_status — verify the cartconfirm_order — submit the orderget_menuget_menu(profile="child1", from_date="2026-06-08", to_date="2026-06-19", as_json=False)Fetches every ISO week overlapping the range (deduped within the call) and filters meals to [from_date, to_date]. With as_json=True returns a list of {date, weekday, meals:[{line_label, menu_line_id, menu_group_id, meal_name, price, max_quantity, already_ordered, orderable, tags}]}.
plan_order (dry run)plan_order(profile="child1", items=[
{"serve_date": "2026-06-08", "menu_line_id": "8"},
{"serve_date": "2026-06-09", "menu_line_id": "9", "menu_group_id": "2"},
], as_json=True)Only GETs weekplans — it never touches the cart. Returns per-line {serve_date, menu_line_id, line_label, meal_name, price, already_ordered, orderable, tags, conflict}, plus order_total (sum of prices for clean, orderable lines) and counts {orderable, conflicts}. conflict is one of:
| conflict | meaning |
|---|---|
null | line is clean and orderable |
no_school_day | that date isn't in the weekplan at all |
unknown_line | the date is present but the menu line wasn't found |
not_orderable | ordering window is closed (ordering_disabled) |
already_ordered | a portion is already ordered for this line |
bulk_add_to_cartbulk_add_to_cart(profile="child1", items=[
{"serve_date": "2026-06-08", "menu_line_id": "8", "quantity": 1},
{"serve_date": "2026-06-09", "menu_line_id": "9"},
])Each item is validated for the ≥1-day-advance rule; items that fail are skipped and reported (the rest still proceed). Returns a per-line ok/skip/fail summary and the final cart total. You still call confirm_order to finalize.
classify_meal) — heuristic / best-effortget_menu and plan_order attach a tags object to each meal:
{ "vegetarian": true, "contains": ["egg"], "sweet": false }vegetarian is true when no pork/beef/poultry/fish keyword matched(egg does not break lacto-ovo vegetarian).
contains lists matched animal categories, a subset of["pork","beef","poultry","fish","egg"].
sweet flags desserts / sweet mains.This is a best-effort keyword heuristic on the German dish name. It does not consult the allergen list or ingredient data — treat it as advisory, not a substitute for the official allergen declaration. The full keyword table and disambiguation rules live in a comment aboveclassify_mealinserver.py.
uv sync # installs dev deps (pytest) too
uv run pytest -q # run the test suite (no network — HTTP is mocked)Tests use httpx.MockTransport and synthetic HTML fixtures; no real IBS5 calls are made.
The server authenticates to IBS5 using a Base64-encoded Bearer token built from the customer ID and password. Credentials are configured server-side via environment variables and never pass through the LLM.
Weekly menus and order history are scraped from server-rendered HTML pages. Cart operations and order confirmation use JSON API endpoints.
MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.