Metadata-Version: 2.4
Name: vivino-mcp
Version: 0.1.6
Summary: MCP server that fetches wine information from Vivino
Keywords: vivino,wine,mcp,model-context-protocol,llm
Author: Tommy Kim
Author-email: Tommy Kim <tommy.dm.kim@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Operating System :: OS Independent
Requires-Dist: cachetools>=5.3
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp>=1.27.2
Requires-Dist: pydantic>=2.13.4
Requires-Dist: truststore>=0.10.4
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/carrtesy/vivino-mcp
Project-URL: Repository, https://github.com/carrtesy/vivino-mcp
Project-URL: Issues, https://github.com/carrtesy/vivino-mcp/issues
Description-Content-Type: text/markdown

# vivino-mcp

An MCP server that fetches wine information from [Vivino](https://www.vivino.com)
via its unofficial JSON API.

> ⚠️ Vivino has **no official public API**. This server calls internal endpoints
> the website uses. It is intended for personal / educational use. Respect
> Vivino's Terms of Service and keep request volume low (the server caches and
> throttles by default). Endpoints may change without notice.

## Tools

| Tool | Description |
|------|-------------|
| `search_wine(query, limit)` | Search wines by name (full catalog, via Vivino's Algolia index — finds wines with no marketplace listing too). |
| `search_by_filters(wine_type, country, min_rating, price_min, price_max, limit)` | Discover/recommend wines by filters, with marketplace prices (via explore). |
| `get_wine_details(vintage_id)` | Full details for a vintage incl. taste structure (tannin/acidity/sweetness/intensity) and flavor profile. |
| `get_wine_reviews(wine_id, year, limit)` | User reviews for a wine. |
| `list_wine_types()` | Supported wine types and their Vivino ids. |

IDs flow naturally: a search result gives you `vintage_id` (for details) and
`wine_id` (for reviews).

`search_wine` / `search_by_filters` return `{total_matched, returned, note,
results}`. `total_matched` is how many wines Vivino reports for the query;
`results` is a ranked, paginated list capped at `limit` (not the full catalog).

## Install (for users)

### Claude Desktop — one-click

Download `vivino-mcp.mcpb` from the
[latest release](https://github.com/carrtesy/vivino-mcp/releases) and
double-click it (or **Settings → Extensions → Install from file**).
Requires [uv](https://docs.astral.sh/uv/) on your machine.

### Any MCP client — via uvx

No clone needed; `uvx` fetches the package from PyPI automatically:

```json
{
  "mcpServers": {
    "vivino": {
      "command": "uvx",
      "args": ["vivino-mcp"]
    }
  }
}
```

Optional env: `VIVINO_CURRENCY` (default `USD`), `VIVINO_COUNTRY` (default `us`).

## Develop (from source)

```bash
uv sync
uv run python smoke_test.py   # optional: live end-to-end check
uv run vivino-mcp             # run the server (stdio)
```

## Configuration (env vars)

| Var | Default | Purpose |
|-----|---------|---------|
| `VIVINO_CURRENCY` | `USD` | Currency for prices/explore. |
| `VIVINO_COUNTRY` | `us` | Default country code. |
| `VIVINO_USER_AGENT` | Firefox UA | Override the browser UA sent to Vivino. |
| `VIVINO_CA_BUNDLE` | — | Path to a CA bundle (PEM) to verify TLS against. |
| `VIVINO_INSECURE` | — | Set to `1` to disable TLS verification (last resort). |

### TLS behind a corporate proxy

By default the server verifies TLS against your **OS trust store** (via
`truststore`), so it works behind corporate TLS-interception proxies whose
private root CA is in the system keychain. If you still hit
`CERTIFICATE_VERIFY_FAILED`, point `VIVINO_CA_BUNDLE` at your company's root CA
PEM, or set `VIVINO_INSECURE=1` as a last resort.

## Architecture

```
server.py   MCP tool layer (FastMCP) — schemas + error wrapping
   │
client.py   VivinoClient — throttle, retry/backoff, TTL cache
   │
models.py   Pydantic models — flatten Vivino's nested JSON
config.py   UA, currency, timeouts, cache TTLs, wine-type map
```

### Verified Vivino endpoints

- `GET /api/explore/explore` — search/discovery. Params: `country_codes[]`,
  `wine_type_ids[]`, `grape_ids[]`, `region_ids[]`, `min_rating`,
  `price_range_min/max`, `currency_code`, `order_by`, `order`, `page`.
  Results at `explore_vintage.matches[]`.
- `GET /api/vintages/{vintage_id}` — vintage detail (no taste data).
- `GET /api/wines/{wine_id}/tastes` — taste structure + flavor profile.
- `GET /api/wines/{wine_id}/reviews?per_page=&page=&year=` — user reviews.

A browser `User-Agent` header is **required** — Vivino blocks requests without one.

## Notes / limitations

- `search_wine` ranks explore results client-side by name match (the explore
  path has no free-text `q` param). For exact-name lookups this is approximate;
  extend with the site search host if you need stricter matching.
- No tests beyond `smoke_test.py` (which hits the live API). Add HTML/JSON
  fixtures for offline unit tests before relying on this in CI.
