Content
# KitchenOwl Insights MCP
[](https://www.python.org/)
[](https://www.docker.com/)
[](https://modelcontextprotocol.io/)
[](https://www.sqlalchemy.org/)
**KitchenOwl Insights MCP** is a standalone, read-only Model Context Protocol server
that estimates which ingredients may still be available in a KitchenOwl household and
ranks recipes already saved in KitchenOwl.
It runs alongside KitchenOwl without modifying or forking it. All estimates include
their supporting evidence and remain explicitly probabilistic.
## Navigation
- [About](#about)
- [Features](#features)
- [MCP tools](#mcp-tools)
- [Tech stack](#tech-stack)
- [Installation](#installation)
- [Configuration](#configuration)
- [MCP client setup](#mcp-client-setup)
- [Verification](#verification)
- [How estimates work](#how-estimates-work)
- [Limitations](#limitations)
- [Contributing](#contributing)
## About
KitchenOwl records when items are added to and removed from shopping lists, but it does
not maintain a confirmed household inventory. This service uses that history to produce
evidence-based estimates without writing back to KitchenOwl.
A `DROPPED` event means that an item was removed from a shopping list. It may represent
a purchase, deletion, or correction, so the service never treats it as a confirmed
purchase. Likewise, an availability estimate does not represent freshness, expiry, or
food safety.
## Features
- Connects to KitchenOwl through a strictly read-only database connection.
- Validates the required KitchenOwl schema before starting.
- Supports SQLite and PostgreSQL through SQLAlchemy.
- Reconstructs item consumption cycles from shopping-list history.
- Estimates availability using deterministic, explainable calculations.
- Reports probability, confidence, evidence, and warnings for every estimate.
- Ranks recipes already saved in the selected KitchenOwl household.
- Separates likely available, uncertain, likely missing, and unknown ingredients.
- Associates recipes and inventory by KitchenOwl item ID rather than item name.
- Runs as a non-root container with a read-only root filesystem configuration.
- Supports `stdio` and Streamable HTTP MCP transports.
## MCP tools
### `get_inventory_estimate`
Returns estimated inventory states for a household, optionally filtered by shopping
list or item IDs.
Example input:
```json
{
"household_id": 1,
"list_id": 1,
"item_ids": [225, 469],
"include_unknown": true
}
```
Possible states are:
- `probably_available`
- `uncertain`
- `probably_missing`
- `unknown`
### `recommend_available_recipes`
Ranks recipes saved in a household according to the estimated availability of their
ingredients.
Example input:
```json
{
"household_id": 1,
"list_id": 1,
"top_k": 5,
"include_optional": false
}
```
Possible classifications are:
- `probably_possible`
- `possible_with_uncertainty`
- `probably_missing_ingredients`
- `insufficient_data`
## How estimates work
The service interprets KitchenOwl history as follows:
- `ADDED`: the item entered a shopping list and likely needs replenishment.
- `DROPPED`: the item left a shopping list and may have been purchased, deleted, or
corrected.
- A complete consumption cycle starts at `DROPPED` and ends at the next `ADDED` event
for the same item.
- Typical duration is the median of complete cycles.
- One or two complete cycles produce low confidence.
- Three or more complete cycles produce medium confidence.
- Legacy list history never produces high confidence.
- An item currently on a shopping list is treated as probably missing.
Recipe scores use the following weights:
| Ingredient state | Weight |
| -------------------- | -----: |
| `probably_available` | 1.00 |
| `uncertain` | 0.50 |
| `unknown` | 0.25 |
| `probably_missing` | 0.00 |
Optional ingredients are excluded from the score by default.
## Limitations
- KitchenOwl does not distinguish purchases from other list-item removals in legacy
history.
- Estimates do not account for free-text quantities.
- Estimates do not indicate freshness, expiry, or food safety.
- Only recipes already saved in KitchenOwl are ranked.
- Sparse history results in low confidence or unknown states.
- KitchenOwl schema changes may require a compatibility update. The service fails closed
when required tables or columns are missing.
- A live SQLite database in WAL mode may require access to its sidecar files. If live
reads are unstable, use a consistent read-only snapshot.
See [schema compatibility](docs/schema-compatibility.md),
[the observed schema](docs/observed-kitchenowl-schema.md), and
[troubleshooting](docs/troubleshooting.md) for additional details.
## Tech stack
- [Python 3.12+](https://www.python.org/)
- [Model Context Protocol Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- [FastMCP](https://github.com/modelcontextprotocol/python-sdk)
- [SQLAlchemy 2](https://www.sqlalchemy.org/)
- [Pydantic Settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/)
- [uv](https://docs.astral.sh/uv/)
- [pytest](https://pytest.org/)
- [Ruff](https://docs.astral.sh/ruff/)
- [Docker](https://www.docker.com/)
## Installation
### Prerequisites
- A running KitchenOwl installation.
- Read-only access to its SQLite or PostgreSQL database.
- Docker and Docker Compose for container deployment, or Python 3.12+ and `uv` for a
local installation.
### Docker Compose with SQLite
The recommended installation uses the published multi-architecture image:
```text
ghcr.io/lukegskw/kitchenowl-insights-mcp:latest
```
Download the Compose example:
```sh
curl -O https://raw.githubusercontent.com/lukegskw/kitchenowl-insights-mcp/main/compose.example.yaml
```
Set the path of the KitchenOwl SQLite database on the Docker host and start the service:
```sh
export KITCHENOWL_DATABASE_PATH=/path/to/kitchenowl/database.db
docker compose -f compose.example.yaml up -d
```
The database file must be readable by UID/GID `10001:10001`. If the KitchenOwl file is
owned by another account, provide a UID/GID that has read access without granting write
access:
```sh
export KITCHENOWL_INSIGHTS_UID_GID=1000:1000
docker compose -f compose.example.yaml up -d
```
To publish a different host port, set:
```sh
export KITCHENOWL_INSIGHTS_PUBLISHED_PORT=18099
docker compose -f compose.example.yaml up -d
```
The `latest` tag follows the newest successful build from the default branch. For a
controlled deployment or rollback, replace it in the Compose file with a published
version or `sha-*` tag.
The Streamable HTTP endpoint will be available at:
```text
http://<host>:8099/mcp
```
The host port can be changed without changing the internal server port. For example,
`18099:8099` publishes the service on host port `18099` while it continues listening on
port `8099` inside the container.
### Build the container from source
Building locally is optional. Prefer the published image above unless you need to
modify or audit the container build.
```sh
git clone https://github.com/lukegskw/kitchenowl-insights-mcp.git
cd kitchenowl-insights-mcp
docker build -t kitchenowl-insights-mcp:local .
docker run --rm --read-only kitchenowl-insights-mcp:local --help
```
### Local Python installation
```sh
git clone https://github.com/lukegskw/kitchenowl-insights-mcp.git
cd kitchenowl-insights-mcp
uv sync --frozen
```
Run against a read-only SQLite database:
```sh
export KITCHENOWL_INSIGHTS_DATABASE_URL='sqlite+pysqlite:///file:/path/to/database.db?mode=ro&uri=true'
uv run kitchenowl-insights
```
### PostgreSQL
Install the optional PostgreSQL driver:
```sh
uv sync --frozen --extra postgres
```
Create a dedicated PostgreSQL role with only:
- `CONNECT` on the database.
- `USAGE` on the required schema.
- `SELECT` on the required tables.
- `default_transaction_read_only=on`.
Then provide a SQLAlchemy URL through a secret:
```text
postgresql+psycopg://<user>:<password>@<database-host>:5432/<database>
```
Do not reuse an administrative KitchenOwl credential. The default container image is
built for SQLite; a PostgreSQL deployment must include the optional `postgres` package
when building the image.
## Configuration
All settings use the `KITCHENOWL_INSIGHTS_` prefix.
| Variable | Required | Default | Description |
| ---------------------------------------- | -------- | --------- | ------------------------------------------------ |
| `KITCHENOWL_INSIGHTS_DATABASE_URL` | Yes | None | Read-only SQLAlchemy database URL. |
| `KITCHENOWL_INSIGHTS_TRANSPORT` | No | `stdio` | `stdio` or `streamable-http`. |
| `KITCHENOWL_INSIGHTS_HOST` | No | `0.0.0.0` | HTTP bind address. |
| `KITCHENOWL_INSIGHTS_PORT` | No | `8099` | Internal HTTP listening port. |
| `KITCHENOWL_INSIGHTS_LOG_LEVEL` | No | `INFO` | Application log level. |
| `KITCHENOWL_INSIGHTS_MAX_HISTORY_EVENTS` | No | `5000` | Maximum history events loaded per request. |
| `KITCHENOWL_INSIGHTS_DEFAULT_TOP_K` | No | `5` | Default number of recipe recommendations. |
| `KITCHENOWL_INSIGHTS_MAX_TOP_K` | No | `20` | Maximum allowed recommendations per request. |
| `KITCHENOWL_INSIGHTS_NOW_OVERRIDE` | No | None | Deterministic clock override intended for tests. |
Secrets must be supplied through the deployment platform or environment and must not be
committed to the repository or included in the container image.
## MCP client setup
For an MCP client that accepts Streamable HTTP server definitions, configure:
```yaml
mcp_servers:
kitchenowl_insights:
url: http://<host>:8099/mcp
```
If the MCP client runs on the same Docker network, use the service name and internal
port instead of publishing a host port:
```yaml
mcp_servers:
kitchenowl_insights:
url: http://kitchenowl-insights:8099/mcp
```
Configuration formats differ between MCP clients. Consult the client documentation for
the exact property names and reload or restart the client after changing its settings.
## Verification
Check the container state and logs:
```sh
docker compose ps
docker compose logs kitchenowl-insights
```
The container should report `healthy` and should not log schema, permission, or database
path errors.
Run the repository verification suite:
```sh
uv sync --frozen
uv run ruff check .
uv run ruff format --check .
uv run pytest -v
```
Verify a local database connection and application-level write protection:
```sh
uv run python scripts/verify_read_only.py \
'sqlite+pysqlite:///file:/path/to/database.db?mode=ro&uri=true'
```
Finally, connect with an MCP client and confirm that it lists both tools. A basic recipe
test should call `recommend_available_recipes` first and use another KitchenOwl
integration only when recipe details or normal KitchenOwl operations are required.
## Contributing
Contributions are welcome. Before opening a pull request:
```sh
uv sync --frozen
uv run ruff check .
uv run ruff format --check .
uv run pytest -v
docker build -t kitchenowl-insights-mcp:test .
```
Changes to database queries must preserve household isolation, schema validation, DSN
redaction, and the two independent read-only protections: database or volume permissions
and application-level rejection of mutable SQL.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available