Skip to main content
Glama
markusl
by markusl
README.md
# Alko MCP Server

A production-grade [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that provides AI assistants with access to the [Alko.fi](https://www.alko.fi/) alcohol product catalog.

## Features

- **Product Search**: Search 11,900+ products by name, type, country, price range, alcohol %, and more
- **Product Details**: Get detailed information including enriched data (taste profile, food pairings, certificates, serving suggestions)
- **Vivino Ratings**: Get wine ratings from Vivino.com by name or URL
- **Store Hours**: Check store opening hours with "open now" filtering
- **Store Availability**: Check real-time stock availability at Alko stores (via web scraping)
- **Recommendations**: Get product recommendations based on food pairings, occasions, and preferences
- **Store Listing**: Browse 360+ Alko stores by city

All tools return **compact JSON** for efficient LLM token usage.

## Demo

<video src="https://github.com/user-attachments/assets/ca9535c2-b3e4-44da-b509-5f55793b9d4a" controls width="100%"></video>

*Claude Desktop using Alko MCP to search products, check availability, and get recommendations.*

## MCP Tools

| Tool | Description |
|------|-------------|
| `search_products` | Search products by name, type, country, price range, alcohol % |
| `get_product` | Get product details. Set `includeEnrichedData=true` for taste, food pairings, serving tips |
| `get_store_hours` | Get store opening hours. Filter by city, name, or `openNow=true`. Auto-refreshes if stale |
| `get_availability` | Check store stock for a product (scrapes alko.fi) |
| `list_stores` | List Alko stores by city |
| `get_recommendations` | Get personalized product recommendations |
| `get_vivino_rating` | Get Vivino wine rating by name or URL (scrapes vivino.com) |
| `sync_products` | Sync database with latest Alko price list |
| `get_sync_status` | Check sync status and product count |

## Quick Start

### Prerequisites

- Node.js 24+
- Google Cloud Firestore (or emulator for local dev)
- Playwright (auto-installed for web scraping)

### Installation

```bash
# Clone the repository
git clone https://github.com/yourusername/alko-mcp.git
cd alko-mcp

# Install dependencies
npm install

# Install Playwright browsers
npx playwright install chromium

# Build
npm run build
```

### Local Development with Firestore Emulator

**Step 1: Start Firestore Emulator** (keep running in background)
```bash
gcloud emulators firestore start --host-port=localhost:8081
```

**Step 2: Start Claude Desktop** (or other AI assistant)

The MCP server will automatically load bundled seed data (~12,000 products, ~360 stores) on first query if the emulator is empty. No manual sync required!

> **Note**: The emulator doesn't persist data. After restarting the emulator, seed data will be auto-loaded again on first use.

#### Optional: Fresh Data Sync

If you need the latest product data from Alko.fi:

```bash
export FIRESTORE_EMULATOR_HOST=localhost:8081

# Sync fresh products from Excel (~30 seconds)
npm run sync-data

# Sync fresh stores from website (~2 minutes)
npm run sync-stores

# Export to seed file (for sharing with team)
npm run export-seed
```

## AI Assistant Configuration

### Claude Desktop

Config file: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)

**Local development:**
```json
{
  "mcpServers": {
    "alko": {
      "command": "node",
      "args": ["/absolute/path/to/alko-mcp/dist/server.js"],
      "env": {
        "FIRESTORE_EMULATOR_HOST": "localhost:8081"
      }
    }
  }
}
```

**Production (Cloud Run):**
```json
{
  "mcpServers": {
    "alko": {
      "url": "https://YOUR-CLOUD-RUN-URL.run.app/mcp",
      "transport": "streamable-http"
    }
  }
}
```

### ChatGPT Desktop

Config file: `~/.config/chatgpt/mcp.json` (macOS/Linux) or `%APPDATA%\chatgpt\mcp.json` (Windows)

**Local development:**
```json
{
  "servers": {
    "alko": {
      "command": "node",
      "args": ["/absolute/path/to/alko-mcp/dist/server.js"],
      "env": {
        "FIRESTORE_EMULATOR_HOST": "localhost:8081"
      }
    }
  }
}
```

**Production (Cloud Run):**
```json
{
  "servers": {
    "alko": {
      "url": "https://YOUR-CLOUD-RUN-URL.run.app/mcp",
      "transport": "streamable-http"
    }
  }
}
```

### Google Gemini (AI Studio)

For Gemini, use HTTP transport. Start the server with:

```bash
MCP_TRANSPORT=http PORT=3000 node dist/server.js
```

Then configure in AI Studio with the MCP endpoint URL:
```
http://localhost:3000/mcp
```

For production, deploy to Cloud Run with API token authentication (see below).

### Claude Code CLI

Add to your project's `.mcp.json`:

```json
{
  "mcpServers": {
    "alko": {
      "command": "node",
      "args": ["./dist/server.js"],
      "env": {
        "FIRESTORE_EMULATOR_HOST": "localhost:8081"
      }
    }
  }
}
```

## Example Prompts

### 🔍 Basic Search
> **Etsi minulle hyviä italialaisia punaviinejä alle 20 euroa**
>
> *Searches for Italian red wines under €20*

### 🍷 Wine Recommendations
> **Suosittele viiniä grillatulle lohelle. Budjetti noin 15-25 euroa.**
>
> *Recommends wine for grilled salmon within budget*

### 🥂 Champagne & Sparkling
> **Mitä samppanjoita Alkossa on saatavilla? Näytä 5 parasta vaihtoehtoa.**
>
> *Lists champagne options*

### 🍺 Craft Beer Search
> **Etsi IPA-oluita Suomesta tai muista Pohjoismaista**
>
> *Searches for Nordic IPA beers*

### 📊 Product Details
> **Kerro lisää tuotteesta numero 906458**
>
> *Gets detailed product information with taste profile*

### 🏪 Store Hours
> **Mitkä Alkon myymälät ovat auki nyt Helsingissä?**
>
> *Lists Helsinki stores that are open now*

### 📍 Store Availability
> **Onko Barolo-viiniä saatavilla Helsingin myymälöissä?**
>
> *Checks product availability in Helsinki stores*

### 🎁 Gift Recommendations
> **Etsi lahjaideoita viininystävälle. Budjetti 50-100 euroa.**
>
> *Premium gift ideas for wine lovers*

### 🧀 Food Pairing (uses Alko's official pairing data)
> **Suosittele viiniä äyriäisille** / **Recommend wine for seafood**
>
> *Uses Alko's food symbol search to find products officially tagged for seafood pairing*

> **Tarvitsen viinin juustolautaselle. Juustot: brie, manchego ja sinihomejuusto.**
>
> *Wine for cheese platter - matches "miedot juustot" and "voimakkaat juustot"*

### 🌍 Region-specific Search
> **Hae espanjalaisia punaviinejä Rioja-alueelta**
>
> *Spanish wines from Rioja region*

### 💰 Budget Shopping
> **Parhaat viinit alle 10 eurolla arki-iltoihin**
>
> *Best budget wines for weeknight dinners*

### 🍾 Special Occasions
> **Suosittele kuohuviiniä uudenvuoden juhliin 20 hengelle**
>
> *Sparkling wine for New Year's party*

### ⭐ Vivino Ratings
> **Etsi punaviinejä 15-25€ ja tarkista niiden Vivino-arvostelut**
>
> *Searches for red wines and checks their Vivino ratings*

### 🏆 Best Rated Wines
> **Mikä on Alkon parhaiten arvioitu Barolo Vivinossa?**
>
> *Finds Barolo wines and compares their Vivino ratings*

### 📈 Wine Comparison
> **Vertaile näiden viinien Vivino-arvosanoja: Amarone, Brunello di Montalcino**
>
> *Compares Vivino ratings for premium Italian wines*

## Data Sources

### Product Catalog
- **Source**: Alko's public Excel price list
- **URL**: `https://www.alko.fi/INTERSHOP/static/WFS/Alko-OnlineShop-Site/-/Alko-OnlineShop/fi_FI/Alkon%20Hinnasto%20Tekstitiedostona/alkon-hinnasto-tekstitiedostona.xlsx`
- **Products**: ~11,900
- **Update**: Run `npm run sync-data`

### Store Data
- **Source**: Scraped from alko.fi store finder
- **Stores**: ~360
- **Includes**: Name, address, opening hours (today/tomorrow)
- **Update**: Run `npm run sync-stores`

### Enriched Product Data
- **Source**: Scraped from individual product pages
- **Includes**: Taste profile, usage tips, serving suggestions, food pairings, certificates, ingredients
- **Cached**: Persisted to Firestore after first scrape

## Product Fields

| Field | Description |
|-------|-------------|
| `id` | Product ID (e.g., "004246") |
| `name` | Product name |
| `producer` | Producer/manufacturer |
| `price` | Price in EUR |
| `pricePerLiter` | Price per liter |
| `bottleSize` | Volume (e.g., "0.75 l") |
| `type` | Category (punaviinit, valkoviinit, oluet, etc.) |
| `subtype` | Flavor profile (e.g., "Mehevä & Hilloinen") |
| `country` | Country of origin |
| `region` | Wine region |
| `alcoholPercentage` | Alcohol % |
| `description` | Taste description from Excel |
| `tasteProfile` | Detailed taste (enriched, scraped) |
| `usageTips` | Usage suggestions (enriched) |
| `servingSuggestion` | Serving temperature (enriched) |
| `foodPairings` | Food pairing symbols (enriched) |
| `certificates` | Certification labels: Luomu, Vegaani, etc. (enriched) |
| `ingredients` | Producer declared ingredients (enriched) |
| `assortment` | vakiovalikoima, tilausvalikoima, etc. |

## Development

```bash
npm run build        # Compile TypeScript
npm run dev          # Run with tsx watch mode
npm run test         # Run tests in watch mode
npm run test:run     # Run tests once (232 tests)
npm run typecheck    # Type check
npm run sync-data    # Sync products from Excel
npm run sync-stores  # Scrape stores from website
npm run export-seed  # Export data to seed file (with diff)
```

### Logs

```bash
tail -f /tmp/alko-mcp.log
```

## Deployment to Google Cloud Run

The server can be deployed to Cloud Run with public access (no authentication) for compatibility with ChatGPT and other MCP clients.

```bash
# Enable APIs
gcloud services enable run.googleapis.com firestore.googleapis.com artifactregistry.googleapis.com cloudbuild.googleapis.com

# Create Firestore database
gcloud firestore databases create --location=europe-north1

# Deploy using Cloud Build
gcloud builds submit --config=cloudbuild.yaml

# Or deploy directly from source
gcloud run deploy alko-mcp \
  --source . \
  --region europe-north1 \
  --memory 2Gi \
  --cpu 2 \
  --execution-environment gen2 \
  --set-env-vars="MCP_TRANSPORT=http" \
  --allow-unauthenticated
```

**Test the endpoint:**
```bash
curl -X POST https://alko-mcp-xxx.a.run.app/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

See [DEPLOYMENT.md](DEPLOYMENT.md) for API token authentication and other options.

## Legal Disclaimer

- The Alko price list is publicly available data
- Web scraping respects rate limits (2s between requests)
- This is an unofficial project not affiliated with Alko Oy
- Alcohol products can only be purchased by persons 18+ in Finland

## License

MIT License

TDQS

A4.3/5.0

Scored across 9 tools

Disambiguation5/5

Every tool has a clearly distinct purpose with no overlap. For example, get_availability checks stock, get_product retrieves product details, and get_recommendations provides personalized suggestions, each targeting different user needs. The descriptions reinforce this separation, making misselection unlikely.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern using snake_case, such as get_availability, list_stores, and search_products. This predictability aids in agent navigation and understanding of tool functions without confusion from mixed conventions.

Tool Count5/5

With 9 tools, the server is well-scoped for its domain of Alko product and store management. Each tool serves a specific role, from data retrieval to administrative tasks, avoiding bloat or thin coverage. This count supports comprehensive workflows without overwhelming complexity.

Completeness4/5

The toolset covers core operations like product search, availability checks, store information, and recommendations, with minor gaps such as missing update or delete operations for products or stores. However, these are likely intentional given the server's focus on read-only data access and synchronization from external sources.

Maintenance

ActivityInactive
ResponsivenessNo issues