Systembolaget API MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Systembolaget API MCP Serverlist new beer launches for March 2025"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Systembolaget API MCP Server
MCP server that exposes the Systembolaget (Swedish liquor store) product search API as a tool. Uses FastMCP and supports configurable launch date range and optional query parameters.
Setup
Clone or open this project.
Create a virtual environment and install dependencies:
python3 -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate pip install -r requirements.txtSet your API key. The API requires the
Ocp-Apim-Subscription-Keyheader. Provide it via environment variable:export SYSTEMBOLAGET_SUBSCRIPTION_KEY=your-subscription-key-hereCopy
.env.exampleto.envand fill in the key if you use a tool that loads.env(do not commit.env).
Related MCP server: Shopify MCP Server
Running the server
From the project root (with the venv activated):
python server.pyOr using the FastMCP CLI:
fastmcp run server.py:mcpThe server runs over stdio by default (for use with Claude Desktop, Cursor, etc.).
You can also run it with uv (same pattern as other local MCP servers):
uv run --directory /path/to/bolki-mcp python server.pyCursor 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:
{
"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
uvpath if different (e.g.uvif it’s on your PATH).Set
SYSTEMBOLAGET_SUBSCRIPTION_KEYto 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 toproductLaunch.min.launch_date_max(required) – End of launch date range,YYYY-MM-DD. Maps toproductLaunch.max.size(optional, default100) – 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, defaultFalse) – IfFalse, returns a compact response for speed; ifTrue, returns the full API-style JSON.
Returns: By default a compact response:
metadata(docCount, totalPages, nextPage, previousPage) andproductswith key fields only (productId, productNameBold, productNameThin, producerName, price, volume, volumeText, alcoholPercentage, country, categoryLevel1, customCategoryTitle, assortmentText, productLaunchDate, taste, usage, vintage). Setfull_response=Trueto 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_maxas 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 assearch_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.
Available Tools
2 toolslist_products_formattedA
Search products by launch date and return a ready-to-use list of formatted lines. All filtering and formatting is done inside the MCP (no client-side parsing needed). Fetches all pages automatically so the list is complete.
Use product_filter="beer" to get only products where CategoryLevel1 is "Öl", or product_filter="all" to get every product in the same format. Each line is "Product Name — volume, alcohol%".
Args: launch_date_min: Start of launch date range (YYYY-MM-DD). launch_date_max: End of launch date range (YYYY-MM-DD). product_filter: "beer" (default) returns only products with CategoryLevel1 "Öl"; "all" returns every product. size: Page size when fetching (default 100). All pages are fetched automatically. sort_by: Sort field. Default "Score". sort_direction: "Ascending" or "Descending". Default "Ascending". assortment_text: Assortment filter. Default "Tillfälligt sortiment".
Returns: Sorted unique list of strings, e.g. ["Vreta klosters Våröl — 330 ml, 5.6%", ...].
| Name | Required | Description | Default |
|---|---|---|---|
| size | No | ||
| sort_by | No | Score | |
| product_filter | No | beer | |
| sort_direction | No | Ascending | |
| assortment_text | No | Tillfälligt sortiment | |
| launch_date_max | Yes | ||
| launch_date_min | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses that all filtering/formatting happens server-side, that all pages are fetched automatically, and that results are sorted and unique. It omits permission/auth requirements and rate-limit behavior, but the operational behavior it does describe is the important part.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then behavior, then a scannable Args block and a concrete Returns example. Slightly redundant in restating defaults already in the schema, but no sentence is truly wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter tool with an output schema, this covers the essentials: purpose, server-side behavior, per-parameter meaning, and an example output line. An output schema exists so return-shape explanation is a bonus rather than a requirement, and the only real gap is the missing sibling differentiation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it does: every one of the 7 parameters is explained, including the date format (YYYY-MM-DD), the meaning of product_filter values ('beer' maps to CategoryLevel1 'Öl'), and defaults for size/sort_by/sort_direction/assortment_text. It stops short of enumerating valid sort_by fields, which keeps it just under a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Search products by launch date') plus a distinctive output contract ('ready-to-use list of formatted lines'). It is clear what the tool does, but it never names or contrasts the sibling search_products, so an agent must infer the split from the title alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explains how to select data via product_filter ('beer' vs 'all') and pins down the default assortment, which is useful tactical guidance. However, it gives no when-to-use/when-not-to-use framing relative to search_products, leaving the central routing decision to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_productsA
Search Systembolaget (Swedish liquor store) products by product launch date range. Fetches all pages automatically and returns the combined result.
Returns products that have a launch date between launch_date_min and launch_date_max. By default returns a compact response (metadata + key product fields only). Set full_response=True to get the complete API-style response (all fields, filters, etc.).
Args: launch_date_min: Start of launch date range (YYYY-MM-DD). Maps to productLaunch.min. launch_date_max: End of launch date range (YYYY-MM-DD). Maps to productLaunch.max. size: Page size used when fetching (default 100). All pages are fetched automatically. sort_by: Sort field (e.g. "Score"). Default "Score". sort_direction: "Ascending" or "Descending". Default "Ascending". assortment_text: Assortment filter (e.g. "Tillfälligt sortiment"). Default "Tillfälligt sortiment". full_response: If False (default), return only metadata and key product fields for speed. If True, return the full response structure (all fields, filters, etc.).
Returns: By default: metadata (docCount, totalPages, nextPage, previousPage) and products with productId, productNameBold, productNameThin, producerName, price, volume, volumeText, alcoholPercentage, country, categoryLevel1, customCategoryTitle, assortmentText, productLaunchDate, taste, usage, vintage. If full_response=True, the complete response with all products and filters is returned.
| Name | Required | Description | Default |
|---|---|---|---|
| size | No | ||
| sort_by | No | Score | |
| full_response | No | ||
| sort_direction | No | Ascending | |
| assortment_text | No | Tillfälligt sortiment | |
| launch_date_max | Yes | ||
| launch_date_min | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does reasonably well: it discloses that all pages are fetched automatically, that the response defaults to a compact form, and enumerates what the compact form contains. It omits auth requirements, rate limits, and behavior on empty/invalid ranges, so it falls short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded (purpose and date-range semantics first, then Args and Returns) and each section is useful. It is somewhat verbose – the Returns block repeats fields the output schema likely already defines – but nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter paged search tool with no annotations, the description covers parameters, pagination, defaults, and return shape, which is enough to invoke it correctly. The one real gap is that it never positions the tool against its sibling or states when not to use it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does: every one of the 7 parameters is documented with type, default, accepted values, and even the upstream field mapping (launch_date_min -> productLaunch.min, YYYY-MM-DD format). This is far beyond what the bare schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Search Systembolaget products') plus the scoping dimension (launch date range) and a notable behavior (automatic pagination). It does not, however, distinguish itself from the sibling list_products_formatted, so an agent cannot tell from the description alone which of the two to pick.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the required date-range parameters, and the description does guide the full_response=True vs default choice. But there is no explicit when-to-use / when-not-to-use statement and no mention of the alternative list_products_formatted.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.1.0- First observed
list_products_formatted - First observed
search_products
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.
Maintenance
Related MCP Connectors
Find concerts, theatre, sport and festivals in Sweden. Authless, read-only MCP.
Search 100+ US retailers, 260M+ products with real-time pricing, stock, and price history. Documentation - https://www.lemmebuyit.com/developer Homepage - https://www.lemmebuyit.com
Riksdagen (Swedish Parliament) open data MCP.
Prices, stock and variants of products on any public Shopify store, with change and margin checks.
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides AI assistants with access to Finland's Alko alcohol product catalog, enabling product search across 11,900+ items, store availability checks, Vivino ratings lookup, and personalized recommendations based on food pairings and preferences.917MIT
- -licenseNot gradedqualityNot gradedmaintenanceProvides integration with Shopify to list and search for products via the Shopify Admin API. It enables users to retrieve product details and perform searches by title or handle within a Shopify store.-
- AlicenseAqualityFmaintenanceQuery Swedish public data from AI tools. Includes company data, SCB statistics, weather, transport, public agencies, and more through the Apiverket API.248 npm2MIT
- AlicenseAqualityAmaintenanceEnables searching Swedish property listings, sold prices, areas, and market statistics via the Booli API using natural language.6750 npm4MIT