migros-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # migros-mcp
2
+
3
+ Search [Migros](https://www.migros.ch) products, get nutrition facts, find stores, and browse promotions with Claude.
4
+
5
+ > **Important**
6
+ > This is **not affiliated with Migros** in any way.
7
+ > It uses publicly accessible API endpoints -- there is no official Migros API.
8
+ > It can **break at any time** if Migros changes their endpoints.
9
+ > No authentication required -- uses guest access only.
10
+ > **Use at your own risk.**
11
+
12
+ ## What you can do
13
+
14
+ Once installed, just talk to Claude naturally:
15
+
16
+ - "Search Migros for protein bars"
17
+ - "What's the nutrition info for this Migros product?"
18
+ - "Find Migros stores near Steinhausen"
19
+ - "What promotions does Migros have right now?"
20
+ - "Compare the protein content of these two products"
21
+ - "Which Migros products are gluten-free?"
22
+
23
+ ## Install
24
+
25
+ No accounts or credentials needed. This server uses Migros guest access.
26
+
27
+ ### Claude Desktop (recommended)
28
+
29
+ 1. Download [migros-mcp.mcpb](https://github.com/lewpgs/migros-mcp/releases/latest/download/migros-mcp.mcpb)
30
+ 2. Install it:
31
+
32
+ **macOS** - Double-click the file, or drag and drop it onto the Claude Desktop app icon
33
+
34
+ **Windows** - In Claude Desktop, go to File > Settings > Extensions > Advanced Settings > Install Extension and select the file
35
+
36
+ That's it. No credentials needed.
37
+
38
+ ---
39
+
40
+ ## Advanced setup
41
+
42
+ These methods require [Node.js 18+](https://nodejs.org) installed on your machine.
43
+
44
+ ### Claude Code
45
+
46
+ ```bash
47
+ claude mcp add migros -- npx -y migros-mcp
48
+ ```
49
+
50
+ ### Claude Desktop (manual) / Cursor
51
+
52
+ Add the following to your config file:
53
+
54
+ - **Claude Desktop** - `claude_desktop_config.json`
55
+ - **Cursor** - `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global)
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "migros": {
61
+ "command": "npx",
62
+ "args": ["-y", "migros-mcp"]
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Test connection
69
+
70
+ ```bash
71
+ npx migros-mcp
72
+ ```
73
+
74
+ ## Available tools
75
+
76
+ | Tool | Description |
77
+ |---|---|
78
+ | `search_products` | Search Migros products by name or keyword. Returns product IDs. |
79
+ | `get_product_details` | Get full product info: nutrition, ingredients, allergens, price, ratings. |
80
+ | `get_categories` | List all Migros product categories. |
81
+ | `search_stores` | Find Migros stores by location with addresses and opening hours. |
82
+ | `get_promotions` | Search current Migros promotions and deals. |
83
+
84
+ ## How it works
85
+
86
+ Migros does not offer a public API. This MCP server uses the [migros-api-wrapper](https://www.npmjs.com/package/migros-api-wrapper) package, which wraps the same endpoints that the Migros website and app use internally. A guest token is obtained at startup (no login required) and cached for the session.
87
+
88
+ Product searches return IDs that you then pass to `get_product_details` for full nutrition tables, ingredients, allergens, and pricing.
89
+
90
+ ## Development
91
+
92
+ ```bash
93
+ git clone https://github.com/lewpgs/migros-mcp.git
94
+ cd migros-mcp
95
+ npm install
96
+ npm run build
97
+ node dist/index.js
98
+ ```
99
+
100
+ Debug with the MCP Inspector:
101
+
102
+ ```bash
103
+ npx -y @modelcontextprotocol/inspector npx migros-mcp
104
+ ```
105
+
106
+ ## Disclaimers
107
+
108
+ - **Unofficial** - This project is not affiliated with, endorsed by, or connected to Migros in any way.
109
+ - **No official API** - This server uses publicly accessible API endpoints that power the Migros website. These are not documented or officially supported. Changes to Migros infrastructure could break this server without notice.
110
+ - **No authentication required** - Uses guest access only. No credentials are stored or transmitted.
111
+ - **Use at your own risk** - No guarantees of functionality, availability, or compatibility.
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { SearchProductsSchema, GetProductDetailsSchema, SearchStoresSchema, GetPromotionsSchema, } from "./schemas.js";
5
+ import { searchProducts, getProductDetails, getCategories, searchStores, getPromotions, } from "./tools.js";
6
+ const server = new McpServer({
7
+ name: "migros-mcp",
8
+ version: "0.1.0",
9
+ });
10
+ // --- Tools ---
11
+ server.tool("search_products", "Search Migros products by name or keyword. Returns product IDs that can be used with get_product_details.", SearchProductsSchema.shape, async ({ query, language }) => {
12
+ try {
13
+ const result = await searchProducts(query, language ?? "de");
14
+ return { content: [{ type: "text", text: result }] };
15
+ }
16
+ catch (error) {
17
+ return {
18
+ content: [{ type: "text", text: `Error: ${error.message}` }],
19
+ isError: true,
20
+ };
21
+ }
22
+ });
23
+ server.tool("get_product_details", "Get full product details including nutrition facts, ingredients, allergens, price, and ratings. Use search_products first to get product IDs.", GetProductDetailsSchema.shape, async ({ productIds }) => {
24
+ try {
25
+ const result = await getProductDetails(productIds);
26
+ return { content: [{ type: "text", text: result }] };
27
+ }
28
+ catch (error) {
29
+ return {
30
+ content: [{ type: "text", text: `Error: ${error.message}` }],
31
+ isError: true,
32
+ };
33
+ }
34
+ });
35
+ server.tool("get_categories", "List all Migros product categories", {}, async () => {
36
+ try {
37
+ const result = await getCategories();
38
+ return { content: [{ type: "text", text: result }] };
39
+ }
40
+ catch (error) {
41
+ return {
42
+ content: [{ type: "text", text: `Error: ${error.message}` }],
43
+ isError: true,
44
+ };
45
+ }
46
+ });
47
+ server.tool("search_stores", "Find Migros stores by location (city name, zip code, etc.). Returns store details including address and opening hours.", SearchStoresSchema.shape, async ({ query }) => {
48
+ try {
49
+ const result = await searchStores(query);
50
+ return { content: [{ type: "text", text: result }] };
51
+ }
52
+ catch (error) {
53
+ return {
54
+ content: [{ type: "text", text: `Error: ${error.message}` }],
55
+ isError: true,
56
+ };
57
+ }
58
+ });
59
+ server.tool("get_promotions", "Search current Migros promotions and deals. Optionally filter by search query.", GetPromotionsSchema.shape, async ({ query }) => {
60
+ try {
61
+ const result = await getPromotions(query ?? "");
62
+ return { content: [{ type: "text", text: result }] };
63
+ }
64
+ catch (error) {
65
+ return {
66
+ content: [{ type: "text", text: `Error: ${error.message}` }],
67
+ isError: true,
68
+ };
69
+ }
70
+ });
71
+ // --- Start ---
72
+ async function main() {
73
+ const transport = new StdioServerTransport();
74
+ await server.connect(transport);
75
+ }
76
+ main().catch((error) => {
77
+ console.error("Fatal error:", error);
78
+ process.exit(1);
79
+ });
80
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,gBAAgB;AAEhB,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,2GAA2G,EAC3G,oBAAoB,CAAC,KAAK,EAC1B,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC5B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;QAC7D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,KAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACvE,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,+IAA+I,EAC/I,uBAAuB,CAAC,KAAK,EAC7B,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACnD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,KAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACvE,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,oCAAoC,EACpC,EAAE,EACF,KAAK,IAAI,EAAE;IACT,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC;QACrC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,KAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACvE,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,eAAe,EACf,wHAAwH,EACxH,kBAAkB,CAAC,KAAK,EACxB,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IAClB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,KAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACvE,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,gFAAgF,EAChF,mBAAmB,CAAC,KAAK,EACzB,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IAClB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAW,KAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACvE,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,gBAAgB;AAEhB,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare function searchProducts(query: string, language: string): Promise<{
2
+ productIds: string[];
3
+ numberOfProducts: number;
4
+ }>;
5
+ export declare function getProductDetails(productIds: string[]): Promise<unknown[]>;
6
+ export declare function getCategories(): Promise<unknown>;
7
+ export declare function searchStores(query: string): Promise<unknown[]>;
8
+ export declare function getPromotions(query: string): Promise<unknown>;
package/dist/migros.js ADDED
@@ -0,0 +1,67 @@
1
+ import { MigrosAPI } from "migros-api-wrapper";
2
+ const migros = new MigrosAPI();
3
+ let cachedToken = null;
4
+ async function getToken() {
5
+ if (cachedToken) {
6
+ return cachedToken;
7
+ }
8
+ const result = await migros.account.oauth2.loginGuestToken();
9
+ cachedToken = result.token;
10
+ return cachedToken;
11
+ }
12
+ /**
13
+ * Reset the cached token so the next call fetches a fresh one.
14
+ * Useful if a request fails due to token expiry.
15
+ */
16
+ function clearToken() {
17
+ cachedToken = null;
18
+ }
19
+ /**
20
+ * Wrapper that retries once with a fresh token on failure.
21
+ */
22
+ async function withToken(fn) {
23
+ try {
24
+ const token = await getToken();
25
+ return await fn(token);
26
+ }
27
+ catch (error) {
28
+ // Token might have expired, try once more with a fresh one
29
+ clearToken();
30
+ const token = await getToken();
31
+ return await fn(token);
32
+ }
33
+ }
34
+ export async function searchProducts(query, language) {
35
+ return withToken(async (token) => {
36
+ const result = await migros.products.productSearch.searchProduct({ query, language: language }, {}, token);
37
+ return {
38
+ productIds: result.productIds ?? [],
39
+ numberOfProducts: result.numberOfProducts ?? 0,
40
+ };
41
+ });
42
+ }
43
+ export async function getProductDetails(productIds) {
44
+ return withToken(async (token) => {
45
+ const result = await migros.products.productDisplay.getProductDetails({ uids: productIds }, token);
46
+ return result ?? [];
47
+ });
48
+ }
49
+ export async function getCategories() {
50
+ return withToken(async (token) => {
51
+ const result = await migros.products.productSearch.categoryList({}, token);
52
+ return result?.categories ?? [];
53
+ });
54
+ }
55
+ export async function searchStores(query) {
56
+ return withToken(async (token) => {
57
+ const result = await migros.stores.searchStores({ query }, token);
58
+ return result ?? [];
59
+ });
60
+ }
61
+ export async function getPromotions(query) {
62
+ return withToken(async (token) => {
63
+ const result = await migros.products.productDisplay.getProductPromotionSearch({ query }, token);
64
+ return result?.items ?? [];
65
+ });
66
+ }
67
+ //# sourceMappingURL=migros.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migros.js","sourceRoot":"","sources":["../src/migros.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ export declare const SearchProductsSchema: z.ZodObject<{
3
+ query: z.ZodString;
4
+ language: z.ZodDefault<z.ZodOptional<z.ZodString>>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ query: string;
7
+ language: string;
8
+ }, {
9
+ query: string;
10
+ language?: string | undefined;
11
+ }>;
12
+ export declare const GetProductDetailsSchema: z.ZodObject<{
13
+ productIds: z.ZodArray<z.ZodString, "many">;
14
+ }, "strip", z.ZodTypeAny, {
15
+ productIds: string[];
16
+ }, {
17
+ productIds: string[];
18
+ }>;
19
+ export declare const SearchStoresSchema: z.ZodObject<{
20
+ query: z.ZodString;
21
+ }, "strip", z.ZodTypeAny, {
22
+ query: string;
23
+ }, {
24
+ query: string;
25
+ }>;
26
+ export declare const GetPromotionsSchema: z.ZodObject<{
27
+ query: z.ZodDefault<z.ZodOptional<z.ZodString>>;
28
+ }, "strip", z.ZodTypeAny, {
29
+ query: string;
30
+ }, {
31
+ query?: string | undefined;
32
+ }>;
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ export const SearchProductsSchema = z.object({
3
+ query: z.string().describe("Search query for Migros products"),
4
+ language: z
5
+ .string()
6
+ .optional()
7
+ .default("de")
8
+ .describe("Language for results (de, fr, it, en). Default: de"),
9
+ });
10
+ export const GetProductDetailsSchema = z.object({
11
+ productIds: z
12
+ .array(z.string())
13
+ .describe("Array of product IDs (UIDs) to get details for. Get these from search_products first."),
14
+ });
15
+ export const SearchStoresSchema = z.object({
16
+ query: z
17
+ .string()
18
+ .describe("Search query for Migros stores (city name, zip code, etc.)"),
19
+ });
20
+ export const GetPromotionsSchema = z.object({
21
+ query: z
22
+ .string()
23
+ .optional()
24
+ .default("")
25
+ .describe("Optional search query to filter promotions"),
26
+ });
27
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;IAC9D,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CAAC,oDAAoD,CAAC;CAClE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,UAAU,EAAE,CAAC;SACV,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;SACjB,QAAQ,CACP,uFAAuF,CACxF;CACJ,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,QAAQ,CAAC,4DAA4D,CAAC;CAC1E,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,4CAA4C,CAAC;CAC1D,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function searchProducts(query: string, language: string): Promise<string>;
2
+ export declare function getProductDetails(productIds: string[]): Promise<string>;
3
+ export declare function getCategories(): Promise<string>;
4
+ export declare function searchStores(query: string): Promise<string>;
5
+ export declare function getPromotions(query: string): Promise<string>;
package/dist/tools.js ADDED
@@ -0,0 +1,120 @@
1
+ import { searchProducts as apiSearchProducts, getProductDetails as apiGetProductDetails, getCategories as apiGetCategories, searchStores as apiSearchStores, getPromotions as apiGetPromotions, } from "./migros.js";
2
+ export async function searchProducts(query, language) {
3
+ const result = await apiSearchProducts(query, language);
4
+ if (result.productIds.length === 0) {
5
+ return JSON.stringify({
6
+ message: `No products found for "${query}"`,
7
+ numberOfProducts: 0,
8
+ productIds: [],
9
+ });
10
+ }
11
+ return JSON.stringify({
12
+ numberOfProducts: result.numberOfProducts,
13
+ productIds: result.productIds,
14
+ hint: "Use get_product_details with these product IDs to get full product info including nutrition, price, and ingredients.",
15
+ }, null, 2);
16
+ }
17
+ function formatNutrition(product) {
18
+ const nutrients = product.nutrientsInformation;
19
+ if (!nutrients?.nutrientsTable?.rows)
20
+ return null;
21
+ const table = nutrients.nutrientsTable;
22
+ const headers = table.headers ?? [];
23
+ const rows = table.rows ?? [];
24
+ if (rows.length === 0)
25
+ return null;
26
+ const headerLabels = headers.map((h) => `${h.label ?? ""}${h.unit ? ` (${h.unit})` : ""}`);
27
+ const lines = [];
28
+ lines.push(`| Nutrient | ${headerLabels.join(" | ")} |`);
29
+ lines.push(`|${"-|".repeat(headerLabels.length + 1)}`);
30
+ for (const row of rows) {
31
+ const values = (row.values ?? []).join(" | ");
32
+ lines.push(`| ${row.label ?? ""} | ${values} |`);
33
+ }
34
+ return lines.join("\n");
35
+ }
36
+ function formatProduct(product) {
37
+ const p = product;
38
+ const formatted = {
39
+ uid: p.uid,
40
+ name: p.name ?? p.title,
41
+ brand: p.brand,
42
+ brandLine: p.brandLine,
43
+ description: p.description,
44
+ origin: p.origin,
45
+ };
46
+ // Price
47
+ const price = p.price;
48
+ if (price) {
49
+ formatted.price = {
50
+ value: price.value,
51
+ unitPrice: price.unitPrice,
52
+ promotionalPrice: price.promotionalPrice,
53
+ };
54
+ }
55
+ // Ratings
56
+ const ratings = p.ratings;
57
+ if (ratings) {
58
+ formatted.ratings = {
59
+ average: ratings.average,
60
+ count: ratings.count,
61
+ };
62
+ }
63
+ // Ingredients
64
+ if (p.ingredients) {
65
+ formatted.ingredients = p.ingredients;
66
+ }
67
+ // Allergens
68
+ if (p.allergenText) {
69
+ formatted.allergens = p.allergenText;
70
+ }
71
+ // Nutrition table (formatted as markdown)
72
+ const nutritionTable = formatNutrition(product);
73
+ if (nutritionTable) {
74
+ formatted.nutrition = nutritionTable;
75
+ }
76
+ // Categories
77
+ const categories = p.categories;
78
+ if (categories && categories.length > 0) {
79
+ formatted.categories = categories.map((c) => c.name ?? c.code).filter(Boolean);
80
+ }
81
+ // GTINs
82
+ if (p.gtins) {
83
+ formatted.gtins = p.gtins;
84
+ }
85
+ // Availability
86
+ const availability = p.productAvailability;
87
+ if (availability) {
88
+ formatted.isAvailable = availability.isAvailable;
89
+ }
90
+ return formatted;
91
+ }
92
+ export async function getProductDetails(productIds) {
93
+ const products = await apiGetProductDetails(productIds);
94
+ if (!products || products.length === 0) {
95
+ return JSON.stringify({
96
+ message: "No product details found for the given IDs",
97
+ productIds,
98
+ });
99
+ }
100
+ const formatted = products.map(formatProduct);
101
+ return JSON.stringify(formatted, null, 2);
102
+ }
103
+ export async function getCategories() {
104
+ const categories = await apiGetCategories();
105
+ return JSON.stringify(categories, null, 2);
106
+ }
107
+ export async function searchStores(query) {
108
+ const stores = await apiSearchStores(query);
109
+ if (!stores || stores.length === 0) {
110
+ return JSON.stringify({
111
+ message: `No stores found for "${query}"`,
112
+ });
113
+ }
114
+ return JSON.stringify(stores, null, 2);
115
+ }
116
+ export async function getPromotions(query) {
117
+ const items = await apiGetPromotions(query);
118
+ return JSON.stringify(items, null, 2);
119
+ }
120
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ export interface GuestTokenResponse {
2
+ token: string;
3
+ }
4
+ export interface ProductSearchResponse {
5
+ productIds: string[];
6
+ numberOfProducts: number;
7
+ categories: unknown[];
8
+ }
9
+ export interface NutrientHeader {
10
+ label: string;
11
+ unit: string;
12
+ }
13
+ export interface NutrientRow {
14
+ label: string;
15
+ values: string[];
16
+ }
17
+ export interface NutrientsTable {
18
+ headers: NutrientHeader[];
19
+ rows: NutrientRow[];
20
+ }
21
+ export interface NutrientsInformation {
22
+ nutrientsTable?: NutrientsTable;
23
+ }
24
+ export interface ProductDetails {
25
+ uid: string;
26
+ miglesId?: string;
27
+ name?: string;
28
+ title?: string;
29
+ description?: string;
30
+ brand?: string;
31
+ brandLine?: string;
32
+ origin?: string;
33
+ ingredients?: string;
34
+ allergenText?: string;
35
+ price?: {
36
+ value?: number;
37
+ unitPrice?: string;
38
+ promotionalPrice?: number;
39
+ };
40
+ nutrientsInformation?: NutrientsInformation;
41
+ ratings?: {
42
+ average?: number;
43
+ count?: number;
44
+ };
45
+ categories?: Array<{
46
+ code?: string;
47
+ name?: string;
48
+ }>;
49
+ images?: Array<{
50
+ url?: string;
51
+ }>;
52
+ gtins?: string[];
53
+ regulatedDescription?: string;
54
+ productAvailability?: {
55
+ isAvailable?: boolean;
56
+ };
57
+ }
58
+ export interface CategoryItem {
59
+ id?: string;
60
+ name?: string;
61
+ slug?: string;
62
+ children?: CategoryItem[];
63
+ }
64
+ export interface CategoryListResponse {
65
+ categories: CategoryItem[];
66
+ }
67
+ export interface StoreResult {
68
+ id?: string;
69
+ name?: string;
70
+ address?: {
71
+ street?: string;
72
+ zip?: string;
73
+ city?: string;
74
+ };
75
+ location?: {
76
+ lat?: number;
77
+ lng?: number;
78
+ };
79
+ openingHours?: Array<{
80
+ day?: string;
81
+ open?: string;
82
+ close?: string;
83
+ }>;
84
+ type?: string;
85
+ }
86
+ export interface PromotionItem {
87
+ id?: string;
88
+ name?: string;
89
+ description?: string;
90
+ price?: {
91
+ value?: number;
92
+ promotionalPrice?: number;
93
+ };
94
+ validFrom?: string;
95
+ validTo?: string;
96
+ }
97
+ export interface PromotionSearchResponse {
98
+ items: PromotionItem[];
99
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "migros-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Migros Switzerland product data, nutrition info, store search, and promotions",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "migros-mcp": "dist/index.js"
8
+ },
9
+ "type": "module",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc && chmod +x dist/index.js",
15
+ "dev": "tsc --watch",
16
+ "start": "node dist/index.js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/lewpgs/migros-mcp.git"
22
+ },
23
+ "homepage": "https://github.com/lewpgs/migros-mcp",
24
+ "bugs": {
25
+ "url": "https://github.com/lewpgs/migros-mcp/issues"
26
+ },
27
+ "author": "lewpgs",
28
+ "keywords": [
29
+ "mcp",
30
+ "migros",
31
+ "switzerland",
32
+ "grocery",
33
+ "nutrition",
34
+ "model-context-protocol"
35
+ ],
36
+ "license": "MIT",
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.12.1",
39
+ "migros-api-wrapper": "^1.1.37",
40
+ "zod": "^3.24.4"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.15.21",
44
+ "typescript": "^5.8.3"
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ }
49
+ }