Skip to main content
Glama
Cisco890

mcp-drink-inventory

by Cisco890
README.md
# MCP Drink Inventory

An independent Python MCP server for local drink inventory, cocktail recipes and food pairings.
Intended public repository name: **mcp-drink-inventory**. This checkout is `mcp/mcp_redes`.
It needs no Anthropic account, API key or host implementation. Only synthetic recipe/demo data
is distributed. Python >=3.12, official MCP SDK v2 (tested with 2.2.0), SQLite and Pydantic.

## Installation and stdio

```bash
cd mcp/mcp_redes
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e '.[dev]'
export INVENTORY_DB_PATH="$PWD/data/demo.db"
python -m mcp_drink_inventory.server
# Equivalent installed entry point:
mcp-drink-inventory
```

The process waits for MCP input; it is not an interactive terminal prompt. stdout belongs to
the protocol and logging goes to stderr. A database is created automatically on the first
inventory tool call. `INVENTORY_DB_PATH` is the only server setting. If unset, storage defaults
to `~/.local/share/mcp-drink-inventory/inventory.db`. Relative paths resolve against process cwd.
The parent directory is created automatically. No network calls or scraping are performed.

## Using any compatible MCP host

Replace both absolute paths below. Install this package in the referenced Python environment.

```json
{
  "mcpServers": {
    "inventory": {
      "command": "/absolute/path/to/.venv/bin/python",
      "args": ["-m", "mcp_drink_inventory.server"],
      "env": {"INVENTORY_DB_PATH": "/absolute/private/path/inventory.db"}
    }
  }
}
```

Uses a hand-rolled NDJSON JSON-RPC stdio server (no SDK). Clients speak MCP `initialize` + `tools/*`.
The sibling course host deliberately uses `Client(..., mode="legacy")` to capture the required
`initialize` sequence while retaining the current v2 SDK.

## Bottle data and validation

Each row represents `quantity` identical bottles with the same remaining percentage. Split rows
when bottles have different fill levels. A 750 ml row with quantity=2 and remaining_percent=50
means **2 bottles and 750 ml total**, not 1 bottle. IDs are internal SQLite integers.

| Field | Type / rule |
|---|---|
| id | Generated integer; not editable |
| beverage_name | Required nonblank string |
| category | Required nonblank string; e.g. whisky, gin, red wine, lime juice |
| brand | Required nonblank string; use a descriptive label for homemade mixers |
| variant | Optional string/null |
| bottle_volume_ml | Finite number >0 |
| remaining_percent | Finite number 0–100, per bottle |
| quantity | Integer >=1, default 1 |
| country, notes | Optional strings/null |
| created_at, updated_at | UTC ISO timestamps; managed by server |

String values are trimmed but original spelling is preserved. Search removes accents and ignores
case, with literal substring matching (no SQL wildcard interpolation). All SQL values are bound
parameters. Updates validate the whole resulting record and reject unknown fields. Transactions
roll back on errors. Connections close after each operation; lock timeout is five seconds.

## Tools and every parameter

All tools are advertised by `tools/list`, including JSON input/output schemas. Results have text
content for general MCP clients; dictionary returns also expose `structuredContent`, and list
returns use the SDK's `{"result": [...]}` structured wrapper. Errors use MCP `isError=true`.

| Tool | Parameters | Return |
|---|---|---|
| `add_bottle` | Required: beverage_name:str, category:str, brand:str, bottle_volume_ml:float, remaining_percent:float. Optional: quantity:int=1, variant:str/null=null, country:str/null=null, notes:str/null=null | Complete Bottle row with generated id/timestamps |
| `list_inventory` | beverage_name:str/null=null, category:str/null=null, brand:str/null=null | Matching Bottle rows, ordered by ID; [] when empty |
| `get_inventory_summary` | Same three optional filters as list_inventory | total_bottles, brands, variants, total_remaining_ml, by_category, bottles |
| `update_bottle` | bottle_id:int, changes:object (one or more editable Bottle fields above) | Complete updated Bottle; errors for missing ID/invalid fields |
| `update_remaining` | bottle_id:int, remaining_percent:float | Complete updated Bottle |
| `remove_bottle` | bottle_id:int | {removed_id:int}; deletes the entire row and its quantity |
| `recommend_cocktails_from_inventory` | None | fully_available, missing_optional, missing_required arrays |
| `get_cocktail_recipe` | cocktail_name:str, servings:int=1 (1–1000) | name, ingredients, steps, glass, garnish, notes, servings |
| `recommend_food_pairings` | beverage_name:str, brand:str/null=null, variant:str/null=null, food_category:str/null=null, limit:int=5 (1–20) | Ordered [{rank, food, category, score, reason}] |
| `seed_demo_inventory` | allow_demo:bool=false | {seeded:true, ids:[...]} or {seeded:false, reason:"already seeded"} |

Summary `bottles` additionally contains `remaining_ml_per_bottle` and `remaining_ml_total`.
`by_category` maps normalized categories to `{bottles, remaining_ml}`. Empty inventory totals are 0.

Recipe ingredients contain `{name, amount, unit, optional}`; amounts scale by servings. The
collection contains 20 authored house versions of classics such as Negroni, Daiquiri, Margarita,
Martini, Manhattan, Paloma and Espresso Martini. Unknown names produce a useful error.

Recommendations compare the available ml with quantities for **one serving**. Match exact
normalized beverage names, categories or variants, with a small explicit Spanish alias map.
Use descriptive names such as `white rum`, `sweet vermouth`, `gin` and `lime juice` to match recipes.
Mixers must also be recorded to count as available. Rows cannot be spent twice within one recipe.
Different recommendations are alternatives, not a claim that all can be made together.
Each result is `{name, missing_required:[names], missing_optional:[names], servings:1}`.
Sort is fewest missing required ingredients, then optional, then name. Ice is assumed; garnish
is optional. Piece-based garnish availability is conservatively unverified by a volume-only DB.

Pairings use beverage category/variety characteristics, not exact brand lookup. For an unfamiliar
wine brand pass its category and grape variety in beverage_name/variant. Food categories include
cheese, charcuterie, meat, seafood, vegetables and dessert; queso/jamón/charcutería are aliases.
Scores are deterministic preference heuristics, not scientific measurements. Unknown characteristics
return [] rather than fabricated matches. Rules cover red/white/sparkling wine, whisky, gin,
tequila/mezcal and rum.

## Synthetic example and prompts

Choose a separate `demo.db`, then call `seed_demo_inventory` with `{"allow_demo":true}`.
It refuses a nonempty unseeded database and records the seed atomically to make retries idempotent.
The six demo rows include two Scotch bottles, one Bourbon, gin, bitter, sweet vermouth and red wine
(seven bottles total). They are fictional examples, not the user's inventory.

- “¿Cuántas botellas de whisky tengo, qué marcas son y cuánto queda de cada una?”
- “Recomienda queso para mi Cabernet Sauvignon.”
- “Quiero preparar un Negroni. Dame medidas y pasos.”
- “¿Qué puedo preparar con lo que tengo y qué me falta?”

## Testing

```bash
ruff check .
pytest -q
```

Includes a real subprocess MCP initialize/tools/list/tools/call integration test, temporary SQLite
storage, CRUD validation, recipe scaling, pairing ranking and stock-aware recommendations.
No API credits or personal database are used.

## Privacy, storage and source attribution

Never commit `.env`, databases, logs, captures, tokens or personal inventories. These are ignored;
review `git ls-files` before publication. Restrict OS access to your database; SQLite is not encrypted.
MCP hosts can read or change inventory through tools and may forward results to an LLM. This
server itself does not contact one. Seeding does not occur on startup.

Recipes and pairing prose are small, project-authored examples based on general culinary knowledge,
not copied from a third-party recipe database. This project has not selected a redistribution
license yet; choose one before public distribution if reuse permissions are intended.
Technical references: [official SDK](https://github.com/modelcontextprotocol/python-sdk),
[MCP documentation](https://py.sdk.modelcontextprotocol.io/).

TDQS

A3.5/5.0

Scored across 10 tools

Disambiguation4/5

Most tools are clearly distinct: CRUD operations, inventory views, and recommendation features each have separate purposes. The only mild overlap is between update_bottle and update_remaining (both modify a bottle row) and between list_inventory and get_inventory_summary, but the descriptions clarify the different intents well enough.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern: add_bottle, list_inventory, remove_bottle, recommend_food_pairings, etc. Even longer names like recommend_cocktails_from_inventory remain structurally consistent and predictable.

Tool Count5/5

With 10 tools, the server is well-scoped for a drink inventory domain that also includes cocktail recommendations and food pairings. Each tool addresses a distinct need without unnecessary redundancy or bloat.

Completeness5/5

The inventory lifecycle is well covered: add, list, summarize, update, adjust remaining, and remove bottles. Cocktail recipe lookup, inventory-based recommendations, food pairings, and demo seeding fill out the domain without obvious dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues