Systembolaget API MCP Server
# Systembolaget API MCP Server
MCP server that exposes the [Systembolaget](https://www.systembolaget.se/) (Swedish liquor store) product search API as a tool. Uses [FastMCP](https://gofastmcp.com/) and supports configurable launch date range and optional query parameters.
## Setup
1. **Clone or open this project.**
2. **Create a virtual environment and install dependencies:**
```bash
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
```
3. **Set your API key.** The API requires the `Ocp-Apim-Subscription-Key` header. Provide it via environment variable:
```bash
export SYSTEMBOLAGET_SUBSCRIPTION_KEY=your-subscription-key-here
```
Copy `.env.example` to `.env` and fill in the key if you use a tool that loads `.env` (do not commit `.env`).
## Running the server
From the project root (with the venv activated):
```bash
python server.py
```
Or using the FastMCP CLI:
```bash
fastmcp run server.py:mcp
```
The server runs over **stdio** by default (for use with Claude Desktop, Cursor, etc.).
You can also run it with [uv](https://docs.astral.sh/uv/) (same pattern as other local MCP servers):
```bash
uv run --directory /path/to/bolki-mcp python server.py
```
## Cursor MCP configuration
Add this server to Cursor’s MCP settings (e.g. **Cursor Settings → MCP** or `~/.cursor/mcp.json`) using stdio and `uv`, in the same way as other local MCP servers:
```json
{
"mcpServers": {
"systembolaget": {
"type": "stdio",
"command": "/opt/homebrew/bin/uv",
"args": [
"run",
"--directory",
"/Users/erik/Development/_dev/bolki-mcp",
"python",
"server.py"
],
"env": {
"SYSTEMBOLAGET_SUBSCRIPTION_KEY": "your-subscription-key-here"
}
}
}
}
```
- Use your actual project path for `--directory` (e.g. `/Users/erik/Development/_dev/bolki-mcp`).
- Use your system’s `uv` path if different (e.g. `uv` if it’s on your PATH).
- Set `SYSTEMBOLAGET_SUBSCRIPTION_KEY` to your API key. Do not commit the key; use a local config or secret.
## Tool: `search_products`
**Fetches all pages automatically** and returns the combined result.
- **Parameters:**
- `launch_date_min` (required) – Start of launch date range, `YYYY-MM-DD`. Maps to `productLaunch.min`.
- `launch_date_max` (required) – End of launch date range, `YYYY-MM-DD`. Maps to `productLaunch.max`.
- `size` (optional, default `100`) – Page size used when fetching; all pages are requested automatically.
- `sort_by` (optional, default `"Score"`) – Sort field.
- `sort_direction` (optional, default `"Ascending"`) – Sort direction.
- `assortment_text` (optional, default `"Tillfälligt sortiment"`) – Assortment filter.
- `full_response` (optional, default `False`) – If `False`, returns a compact response for speed; if `True`, returns the full API-style JSON.
- **Returns:** By default a **compact response**: `metadata` (docCount, totalPages, nextPage, previousPage) and `products` with key fields only (productId, productNameBold, productNameThin, producerName, price, volume, volumeText, alcoholPercentage, country, categoryLevel1, customCategoryTitle, assortmentText, productLaunchDate, taste, usage, vintage). Set `full_response=True` to get the full response (all fields, filters, etc.) when needed.
### Tool: `list_products_formatted`
Returns a **ready-to-use list of lines** (filtering and formatting done in the MCP; no client-side parsing). **Fetches all pages automatically** so the list is complete.
- **Parameters:** Same `launch_date_min`, `launch_date_max` as above, plus:
- `product_filter`: `"beer"` (default) – only products with CategoryLevel1 "Öl"; `"all"` – every product.
- `size` – page size used when fetching (default 100); all pages are requested automatically.
- `sort_by`, `sort_direction`, `assortment_text` – same as `search_products`.
- **Returns:** Sorted unique list of strings, e.g. `["Vreta klosters Våröl — 330 ml, 5.6%", ...]`. Use this instead of running Python scripts to parse search results.
## License
Use of the Systembolaget API is subject to Systembolaget’s terms. This project is for integration only.
TDQS
Scored across 2 tools
Both tools perform the same core operation — searching products by launch date range with identical arguments (size, sort_by, sort_direction, assortment_text) and the same auto-pagination behavior. The only real difference is output formatting (raw structured data vs. pre-formatted strings), which makes list_products_formatted a redundant variant of search_products rather than a distinct capability.
Both names are snake_case with a verb_noun shape, which is readable and consistent in style. However, the verbs diverge ('search' vs 'list') despite the tools doing the same underlying search, which weakens predictability about which tool to pick for a lookup.
Two tools is thin for a product catalog API, but the second is a formatting convenience wrapper rather than a genuinely separate function, so the effective surface is closer to one tool with a format flag. This is borderline rather than severely mismatched.
The surface only supports launch-date-range queries and cannot search by name, producer, category, price, or alcohol content, and there is no get-by-ID, store lookup, or stock/availability tool — all core to the Systembolaget domain. Agents would hit dead ends for most real product-lookup tasks.