Skip to main content
Glama
README.md
<!-- mcp-name: io.github.OriginalByteMe/our-groceries-mcp -->

# OurGroceries MCP

An **unofficial** [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for an OurGroceries account. It relies on an unofficial API client; upstream behavior, availability, and compatibility can change without notice. Review every result and verify important changes in OurGroceries.

> [!WARNING]
> The Streamable HTTP server has **no built-in application authentication**. Never expose it directly to the internet: anyone who can reach it can use the configured grocery account. Bind it to localhost, or put it behind an **authenticated, trusted tunnel or reverse proxy** that enforces authentication before forwarding requests.

## Prerequisites

- An OurGroceries account and its credentials.
- For a source install: [uv](https://docs.astral.sh/uv/) and Python 3.12 or later.
- For the container option: Docker.
- An MCP client for stdio or Streamable HTTP.

The server reads `OURGROCERIES_EMAIL` and `OURGROCERIES_PASSWORD` from its process environment. `.env` is a local template, not an automatically loaded configuration file. Keep it private and never commit it.

## Install and configure

### Source with uv

```bash
git clone https://github.com/OriginalByteMe/our-groceries-mcp.git
cd our-groceries-mcp
cp .env.example .env
# Edit .env locally; do not commit it.
uv sync --locked
```

Set placeholders in `.env` only on your machine:

```dotenv
OURGROCERIES_EMAIL=""
OURGROCERIES_PASSWORD=""
```

For a shell-launched server, export the file first:

```bash
set -a
. ./.env
set +a
```

### Docker

The image starts the Streamable HTTP transport. Build it and publish the port only on localhost:

```bash
docker build -t our-groceries-mcp .
docker run --rm --env-file .env -p 127.0.0.1:8000:8000 our-groceries-mcp
```

Its MCP endpoint is `http://127.0.0.1:8000/mcp`. This is an MCP endpoint, not a browser UI. If remote access is necessary, retain the localhost binding and use an authenticated trusted tunnel or reverse proxy; do not forward the port unauthenticated.

### Install the agent skill

For agents that support Vercel Skills, install the repository skill:

```bash
npx skills add OriginalByteMe/our-groceries-mcp --skill our-groceries-mcp
```

This installs guidance, not credentials. Configure credentials locally in the MCP client or environment.

## Connect an MCP client

### Stdio (recommended)

After `uv sync --locked`, add this JSON to your client's MCP server configuration. Replace the absolute checkout path and placeholders locally; do not put real credentials in a shared config file.

```json
{
  "mcpServers": {
    "our-groceries": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/our-groceries-mcp",
        "--locked",
        "our-groceries-mcp"
      ],
      "env": {
        "OURGROCERIES_EMAIL": "",
        "OURGROCERIES_PASSWORD": ""
      }
    }
  }
}
```

Stdio keeps the server local to the client process and is the safest default.

### Streamable HTTP

For a source checkout, after exporting the environment variables, run:

```bash
uv run --locked our-groceries-mcp \
  --transport streamable-http \
  --host 127.0.0.1 \
  --port 8000
```

Connect an MCP client to `http://127.0.0.1:8000/mcp`. The endpoint has no HTTP authentication. Do not use `0.0.0.0` or a public host unless an authenticated trusted tunnel or reverse proxy protects every request.

## Read-only smoke check

From a configured source checkout, this launches the server over stdio, initializes MCP, and calls the real read-only `list_grocery_lists` tool. It prints no grocery-list contents.

```bash
set -a
. ./.env
set +a
uv run --locked python - <<'PY'
import asyncio
import os

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client


async def main() -> None:
    server = StdioServerParameters(
        command="uv",
        args=["run", "--locked", "our-groceries-mcp"],
        env=dict(os.environ),
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("list_grocery_lists", {})
            if result.isError:
                raise RuntimeError("list_grocery_lists failed")
    print("Read-only MCP smoke check passed.")


asyncio.run(main())
PY
```

This contacts the live OurGroceries account. Do not run it with credentials you do not intend to use.

## Tools and confirmation

List and item names are resolved by case-insensitive exact match. Resolve ambiguity with `list_grocery_lists` or `get_items` before a mutation.

| Tool | Parameters | Behavior |
| --- | --- | --- |
| `list_grocery_lists` | none | Read available lists. |
| `get_items` | `list_name` | Read items in one list. |
| `add_item` | `list_name`, `item`, optional `quantity`, `note` | Adds immediately; it has no confirmation flag. Ask before calling it. |
| `complete_item` | `list_name`, `item`, optional `confirm` | Returns a preview by default. Marking complete requires `confirm: true`. |
| `remove_item` | `list_name`, `item`, optional `confirm` | Returns a preview by default. Permanent removal requires `confirm: true`. |
| `move_item` | `source_list`, `destination_list`, `item`, optional `confirm` | Returns a preview by default. Moving requires `confirm: true`. |

For `complete_item`, `remove_item`, and `move_item`, first call with the default `confirm: false`, show the preview, and only call again with `confirm: true` after explicit user approval. `add_item` is an immediate write, so obtain approval before the first call.

> [!CAUTION]
> `move_item` is not atomic: it adds the item to the destination, then removes it from the source. If removal fails, the item can exist in both lists. Inspect both lists before retrying so a retry does not create another duplicate.

## Troubleshooting

- **Credentials are not configured:** pass both environment variables to the server process; `.env` alone is not loaded automatically.
- **Authentication failed or requests fail:** verify the account credentials and network, then retry later. This is an unofficial integration and upstream API changes can break it.
- **List or item not found / ambiguous:** use the read tools and provide one case-insensitive exact name; rename duplicate lists or items in OurGroceries if needed.
- **Cannot connect over HTTP:** ensure the server is running, use `/mcp` (not `/`), and keep the host/port local unless an authenticated proxy or tunnel is in place.
- **A move reports partial failure:** check both source and destination lists manually before deciding whether to remove, move, or retry the item.

## Development and testing

Install development dependencies and run the offline test suite with the lockfile:

```bash
uv sync --locked --all-groups
uv run --locked pytest -m "not live"
```

Tests marked `live` require a deliberately configured live account and are excluded by default. Do not add credentials to test output, fixtures, or committed files.

## Security

See [SECURITY.md](SECURITY.md) before enabling Streamable HTTP. In particular, direct unauthenticated internet exposure grants grocery-account access to callers.

TDQS

A3.8/5.0

Scored across 6 tools

Disambiguation5/5

Each tool has a clear, distinct purpose: listing lists, getting items, adding, completing, removing, and moving items. There is no overlap or ambiguity between these actions.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (e.g., list_grocery_lists, add_item, complete_item). The naming is uniform and predictable.

Tool Count5/5

With 6 tools, the server is well-scoped for grocery list management. Each tool addresses a distinct operation, and the count is neither too sparse nor overwhelming.

Completeness3/5

The tool set covers item-level operations (add, get, complete, remove, move) but lacks list-level create/delete operations and any way to update item details like quantity or note after creation. These are notable gaps for a grocery list management server.