Shelf Life MCP
by mgnlia
README.md
# Shelf Life MCP
[](https://github.com/mgnlia/shelf-life-mcp/actions/workflows/ci.yml)
[](LICENSE)
A self-hosted **Model Context Protocol (MCP) server** that tracks household
shelf life — what is in the fridge, what is about to expire, and what to cook
first. Built as an **Alexa+ Agent Skill** entry, it speaks the **open MCP spec
(2025-11-25+)** over **Streamable HTTP** and persists everything in a **local
SQLite** file. No cloud dependency, no partner-only SDK.
> **License:** MIT — see [LICENSE](LICENSE) at the top of the repo.
---
## Why this exists
Alexa+ "Agent Skills" are MCP servers. This project implements that contract
against the *open* specification so it can be demoed with the **MCP Inspector**
or any generic MCP client, rather than depending on Amazon's partner-only
Category SDK / MCP Toolkit.
## Language choice
**Python 3.10+ with `uv`.** Rationale:
- The official `mcp` Python SDK (v2) ships a first-class `MCPServer` class and a
`Client` that can connect **in memory**, which makes a passing test per tool
trivial and hermetic — no subprocess, no port, no flaky network.
- `uv` gives reproducible, fast dependency resolution and a lockfile.
- SQLite is in the standard library, so persistence adds zero runtime deps.
## Features
| Tool | Purpose |
|------|---------|
| `add_item` | Record a food item with an expiry date. |
| `list_items` | List items, filtered by `all` / `expiring` / `expired` / `consumed`. |
| `expiring_soon` | Items expiring within N days (default 7), soonest first. |
| `consume_item` | Reduce quantity; auto-marks consumed at zero. |
| `suggest_recipe` | Deterministic "use it up" suggestion; urgency or FIFO ranking. |
## Architecture
```
mcp.json ──► config.load_config() ──► MCPServer (server.py)
│
├── add_item ┐
├── list_items │
├── expiring_soon ├─► Store (store.py) ──► SQLite
├── consume_item │
└── suggest_recipe ┘
│
mcp.run(transport="streamable-http", ...)
```
- **`mcp.json`** is a real MCP config document. It is **loaded in code** at
import time (`server.py` → `load_config()`), and it drives the transport host,
port, path, protocol version, and database location. It is not decorative.
- **`config.py`** validates the config with Pydantic and resolves paths.
- **`store.py`** owns all SQLite access (schema, queries, the recipe heuristic).
- **`server.py`** registers the five tools and derives `mcp.run()` kwargs from
the loaded config.
## Setup
Requires Python 3.10+ and [`uv`](https://docs.astral.sh/uv/).
```bash
git clone https://github.com/mgnlia/shelf-life-mcp.git
cd shelf-life-mcp
uv sync --dev
```
## Run
Streamable HTTP (the transport declared in `mcp.json`):
```bash
uv run shelf-life-mcp
# or:
uv run python -m shelf_life_mcp
```
The server listens on `http://127.0.0.1:8000/mcp` (host/port/path come from
`mcp.json`). Override the config or database without editing files:
```bash
SHELF_LIFE_CONFIG=/path/to/mcp.json SHELF_LIFE_DB=/tmp/demo.db uv run shelf-life-mcp
```
### Try it with the MCP Inspector
```bash
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP
# URL: http://127.0.0.1:8000/mcp
```
## MCP client configuration
Drop this into your MCP client's config (Claude Desktop, Cursor, VS Code, etc.).
The same document lives in [`mcp.json`](mcp.json) in this repo.
```json
{
"mcpServers": {
"shelf-life": {
"type": "streamable-http",
"url": "http://127.0.0.1:8000/mcp",
"transport": { "host": "127.0.0.1", "port": 8000, "path": "/mcp" },
"database": "./shelf_life.db",
"protocolVersion": "2025-11-25"
}
}
}
```
## Tool schemas
Generated from the Python type hints; `output_schema` is derived from each
return annotation.
### `add_item`
```json
{
"name": "add_item",
"input_schema": {
"type": "object",
"properties": {
"name": { "type": "string", "title": "Name" },
"expires_on": { "type": "string", "title": "Expires On" },
"qty": { "type": "number", "default": 1, "title": "Qty" },
"unit": { "type": "string", "default": "item", "title": "Unit" }
},
"required": ["name", "expires_on"]
}
}
```
### `list_items`
```json
{
"name": "list_items",
"input_schema": {
"type": "object",
"properties": {
"filter": { "type": "string", "default": "all", "title": "Filter" }
},
"required": []
}
}
```
`filter` ∈ `all` | `expiring` | `expired` | `consumed`.
### `expiring_soon`
```json
{
"name": "expiring_soon",
"input_schema": {
"type": "object",
"properties": { "days": { "type": "integer", "default": 7, "title": "Days" } },
"required": []
}
}
```
### `consume_item`
```json
{
"name": "consume_item",
"input_schema": {
"type": "object",
"properties": {
"id": { "type": "integer", "title": "Id" },
"qty": { "type": "number", "default": 1, "title": "Qty" }
},
"required": ["id"]
}
}
```
### `suggest_recipe`
```json
{
"name": "suggest_recipe",
"input_schema": {
"type": "object",
"properties": {
"use_first": { "type": "boolean", "default": true, "title": "Use First" }
},
"required": []
}
}
```
`use_first` changes how the suggested ingredients are ranked:
- `use_first: true` (default) — rank by **urgency**: already-expired items
first, then the soonest expiry, then name. The dish is named after the most
urgent ingredient.
- `use_first: false` — ignore urgency and rank by **stock age** (FIFO): items
added earliest come first, so the oldest stock is cleared before it spoils.
The dish is named after the longest-held ingredient.
Both modes consider only unconsumed items expiring within 7 days and cap the
suggestion at three ingredients. When nothing is close to expiring, the tool
returns an empty `use_first` list and a "shop fresh" rationale.
## Tests
One passing test per tool, run through the in-memory MCP client against the real
registered tools:
```bash
uv run pytest
```
Covered: `add_item`, `list_items`, `expiring_soon`, `consume_item`,
`suggest_recipe` (both `use_first` modes), plus tool-registration and
config/edge-case tests.
## Project layout
```
shelf-life-mcp/
├── mcp.json # MCP config, loaded in code
├── pyproject.toml
├── src/shelf_life_mcp/
│ ├── config.py # MCP config loading + validation
│ ├── store.py # SQLite persistence + recipe heuristic
│ ├── server.py # MCPServer, tools, transport
│ └── __main__.py
└── tests/
├── conftest.py
├── test_tools.py # one test per tool
└── test_config.py
```
## License
MIT © 2026 Shelf Life MCP contributors.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues