Skip to main content
Glama
bsosik1

Sushimaster

by bsosik1
README.md
# Sushimaster

<p align="center">
  <img src="assets/banner.png" alt="Sushimaster banner" width="80%">
</p>

**MCP tool for AI agents** (Claude Code, Claude Desktop, Codex) that finds
deals in food delivery apps. Ask your agent *"find a pizza under 50 PLN with
free delivery"* and it will search Wolt and Glovo, compare prices, ratings,
delivery estimates and promotions, and return a ready recommendation with a link.

## The problem it solves

<p align="center">
  <picture>
    <source media="(max-width: 640px)" srcset="assets/problem-solution-mobile.svg">
    <img src="assets/problem-solution.svg" alt="Sushimaster searches delivery apps, compares offers and returns the best deal" width="100%">
  </picture>
</p>

<p align="center">
  <strong>No account. No API keys. No manual configuration.</strong><br>
  Just share this repository link with your agent and ask it to install
  Sushimaster. The agent will clone the repo, set everything up and connect
  the MCP server on its own. That's it.
</p>

<p align="center">
  <strong>Built to grow.</strong><br>
  The architecture is provider-agnostic. Adding new apps such as Uber Eats or
  Deliveroo means implementing one class that subclasses <code>BaseProvider</code>.
</p>

---

## Features

| MCP tool | Description |
|---|---|
| `list_providers` | List available food delivery apps |
| `resolve_location` | Resolve an address / `lat,lon` to coordinates |
| `search_venues` | Find restaurants (rating 0–10, ETA, free-delivery flags, promotions) |
| `search_items` | Find dishes with prices in cents; filter by price, rating, free delivery |
| `get_venue_menu` | Fetch a full restaurant menu (discounted items included) |

* No login, no API keys — data comes from the public Wolt and Glovo endpoints.
* Prices are normalized to cents (int), ratings to a 0–10 scale.
* Every result is tagged with `fetched_at` and a per-provider report
  (responded?, latency, warnings) so the agent can attribute the data honestly.
* Results are cached (TTL) with single-flight protection.

## Requirements

* Python **3.10+**
* [uv](https://docs.astral.sh/uv/) (for installation and running)

## Installation

```bash
cd sushimaster
uv sync --extra dev      # create .venv and install dependencies (+ dev tools)
```

This installs two console commands into `.venv/bin/`:

| Command | Purpose |
|---|---|
| `sushimaster` | CLI for quick testing (resolve / venues / items / menu) |
| `sushimaster-mcp` | MCP server (stdio transport) |

To call them as plain `sushimaster …` / `sushimaster-mcp`, either activate the
virtualenv (`source .venv/bin/activate`) or add `.venv/bin` to your `PATH`.
Alternatively, use `uv run sushimaster …` from the project directory.

## Usage

### CLI (quick testing without MCP)

```bash
sushimaster resolve "Warszawa, Marszałkowska 98"
sushimaster venues "pizza" "52.2297,21.0122" --apps wolt,glovo --free-delivery
sushimaster items "pizza margherita" "Warszawa" --max-price 5000 --min-rating 8
sushimaster menu glovo "dodo-pizza-waw" "52.2297,21.0122"
```

### MCP server (stdio)

```bash
sushimaster-mcp
```

Claude Code configuration (`claude mcp add`):

```bash
claude mcp add sushimaster -- /path/to/sushimaster/.venv/bin/sushimaster-mcp
```

Or in `~/.claude.json`:

```json
{
  "mcpServers": {
    "sushimaster": {
      "command": "/path/to/sushimaster/.venv/bin/sushimaster-mcp",
      "args": []
    }
  }
}
```

Once connected, try: *"Find a pizza under 50 PLN with free delivery in Warsaw."*

## Instructions for agents

This section is written for AI agents (Claude Code, Codex, Claude Desktop) that
need to install and use Sushimaster. Follow the steps in order.

### 0. Prerequisites

* Python **3.10+** — check with `python3 --version`
* [uv](https://docs.astral.sh/uv/) — check with `uv --version`
* `git` — check with `git --version`

### 1. Clone and install

```bash
git clone <repository-url> sushimaster
cd sushimaster
uv sync --extra dev        # creates .venv/ and installs the package
```

This produces two executables in `.venv/bin/`: `sushimaster` (CLI) and
`sushimaster-mcp` (MCP server). Remember the **absolute path** to
`sushimaster-mcp` — you will need it in step 2. Get it with:

```bash
echo "$(pwd)/.venv/bin/sushimaster-mcp"
```

### 2. Register the MCP server

Pick the section matching your agent.

**Claude Code** (project-scoped, recommended):

```bash
claude mcp add sushimaster -- /absolute/path/to/sushimaster/.venv/bin/sushimaster-mcp
```

Then restart Claude Code and verify with `/mcp` — the `sushimaster` server
should be listed as *connected* with 5 tools.

**Claude Code** (alternative — `.mcp.json` in the project root, committed to
the repo):

```json
{
  "mcpServers": {
    "sushimaster": {
      "command": "/absolute/path/to/sushimaster/.venv/bin/sushimaster-mcp",
      "args": []
    }
  }
}
```

**Codex:**

```bash
codex mcp add sushimaster -- /absolute/path/to/sushimaster/.venv/bin/sushimaster-mcp
```

**Claude Desktop** — add the same `mcpServers` entry to
`claude_desktop_config.json` (Claude → Settings → Developer → Edit config),
then restart Claude Desktop.

> Do **not** use `uv run` or `python -m` as the `command` — the installed
> script is self-contained and avoids spawning an extra process.

### 3. Verify the installation

From the shell:

```bash
/absolute/path/to/sushimaster/.venv/bin/sushimaster resolve "Warszawa"
```

Expected: JSON with `lat`, `lon`, `label` and `country_code: "PL"`.

From the agent, ask: *"list your available tools"* or call `list_providers`.
Expected: `[{"name": "wolt", "display_name": "Wolt"}, {"name": "glovo", "display_name": "Glovo"}]`.

Then run an end-to-end query:

> *"Find a pizza margherita under 40 PLN in Warsaw and list the results with prices."*

### 4. Tool reference for agents

| Tool | Purpose | Key parameters |
|---|---|---|
| `list_providers` | Available apps | — |
| `resolve_location` | Address / `lat,lon` → coordinates | `address` |
| `search_venues` | Restaurants | `query`, `address`, `apps`, `free_delivery`, `min_rating`, `limit` |
| `search_items` | Dishes | `query`, `address`, `max_price_cents`, `min_rating`, `free_delivery`, `sort`, `limit` |
| `get_venue_menu` | Restaurant menu | `provider`, `venue_id`, `address` |

Conventions the agent should follow when answering users:

* Prices are in **cents** — divide by 100 for PLN (e.g. `3699` → 36.99 zł).
* Ratings are **0–10**.
* Always mention the **source app** (Wolt/Glovo) and the delivery flag
  (`free_delivery`) for each recommendation.
* `venue_id` from search results can be passed straight to `get_venue_menu`.
* Read the `warnings` array — it may explain partial data (e.g. Wolt menu
  previews) or failed providers.

### 5. Run the tests

```bash
cd sushimaster
uv run pytest             # offline tests on frozen API fixtures
uv run pytest -m live     # optional: live tests against real APIs
```

### 6. Troubleshooting

| Symptom | Fix |
|---|---|
| `sushimaster-mcp: command not found` | Run `uv sync --extra dev` again; check the path from step 1 |
| MCP server listed as *failed* | Restart the agent; verify the absolute path has no symlinks/quotes |
| `No module named 'yaml'` / unrelated plugin errors | `PYTHONPATH` from another toolchain (e.g. ROS) leaks in — run `PYTHONPATH= uv run pytest` |
| Tools return empty `providers` | `SUSHIMASTER_PROVIDERS` env var may be filtering them — unset it or list the apps explicitly |
| No results for a city | The app may not cover that city (Glovo is limited to larger cities in Poland) — try `resolve_location` first |

## Configuration (environment variables)

| Variable | Default | Description |
|---|---|---|
| `SUSHIMASTER_CACHE_TTL` | `600` | Result cache TTL in seconds |
| `SUSHIMASTER_PROVIDERS` | empty (all) | Active providers, comma-separated, e.g. `wolt,glovo` |
| `SUSHIMASTER_REQUEST_INTERVAL` | `0.35` | Minimum interval between API requests (seconds) |
| `SUSHIMASTER_TIMEOUT` | `15` | HTTP request timeout (seconds) |

## Project structure

```
src/sushimaster/
├── server.py            # MCP server (FastMCP) + tool definitions
├── service.py           # orchestration: caching, filtering, dedup, reports
├── geo.py               # address → coordinates (multi-provider resolution)
├── models.py            # shared, normalized schema (Venue, MenuItem, …)
├── cache.py             # TTL cache with single-flight
└── providers/
    ├── base.py          # BaseProvider — the contract for new providers
    ├── wolt.py          # Wolt adapter
    └── glovo.py         # Glovo adapter
```

## Adding a new provider

1. Create `src/sushimaster/providers/<name>.py` subclassing `BaseProvider`.
2. Set `name` / `display_name` and implement the three abstract methods:
   * `search_venues(query, location, *, limit) -> list[Venue]`
   * `search_items(query, location, *, limit) -> list[MenuItem]`
   * `get_venue_menu(venue_id, location) -> MenuFetchResult`
3. Optionally implement `geocode()` and `check_availability()`.
4. Register the class with the `@register` decorator and add the module import
   in `providers/__init__.py` (importing the module runs the decorator).
5. Done — caching, filtering, dedup and the MCP tools pick it up automatically.

Every provider gets, for free: HTTP with retry/backoff and error mapping
(429 → `RateLimitError`), an enforced minimum interval between requests,
price parsing (`_to_cents` handles `"36,99 zł"`, `3699`, `36.99`) and rating
normalization (`_normalize_rating` maps `"98%"` → `9.8`).
The full contract is documented in the `providers/base.py` docstring.

## Known limitations

* **Full Wolt menus** require a web session; the adapter returns dish previews
  from the restaurant list and always reports this as a warning. Glovo provides
  complete menus.
* **Wolt delivery fees** are calculated dynamically at the basket level — the
  adapter exposes approximation flags (`delivery_price_highlight`, Wolt+).
  Glovo returns the real fee and the `isFreeDeliveryFee` flag directly.
* **Personal promotions** (discount codes, Wolt+/Prime prices) require an
  authenticated session and are not visible to this tool.
* **Glovo coverage** in Poland is limited to larger cities.

## Privacy & data handling

* The tool only reads **public** data (venues, menus, prices) from provider APIs.
* It performs **no authentication** and stores **no user data** on disk.
* The in-memory result cache holds only public search results and expires
  automatically (TTL).
* No telemetry, no tracking, no third-party services — geocoding is done
  through the providers' own endpoints.

## Tests

```bash
uv run pytest             # offline tests on frozen API responses (fixtures)
uv run pytest -m live     # live tests against the real APIs (1s request interval)
```

Offline tests use fixtures from `tests/fixtures/` — real API responses captured
during development, so parser regressions are caught without the network.
To regenerate fixtures, capture fresh API responses and replace the files.

> Note: in environments with `PYTHONPATH` set (e.g. ROS), run pytest with
> `PYTHONPATH=` to avoid loading unrelated plugins.

## Disclaimer

Sushimaster is an **unofficial** project. It is not affiliated with, endorsed
by, or sponsored by Wolt or Glovo. The provider endpoints are public but
undocumented and may change or require authentication at any time; use the
tool at your own risk and respect each platform's terms of service and rate
limits (the built-in request pacing is there to help with that).

TDQS

A4.1/5.0

Scored across 5 tools

Disambiguation5/5

Each tool serves a distinct purpose: listing providers, resolving locations, searching restaurants, searching dishes, and fetching menus. There is no overlap in functionality, and the parameters are tailored to each operation.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern: list_providers, resolve_location, search_venues, search_items, get_venue_menu. The verbs are specific and the nouns clearly indicate the resource.

Tool Count5/5

With 5 tools, the server is well-scoped for food delivery discovery. Each tool covers a necessary step in the workflow without redundancy or bloat.

Completeness5/5

The tool surface covers the full discovery lifecycle: identify available apps, resolve a location, search for venues or items, and retrieve a venue menu. No obvious missing operations exist for the stated purpose.

Maintenance

ActivitySlowing
ResponsivenessNo issues