# Research: Swiggy MCP Integration — Verified Working Configuration

**Date:** 2026-04-02
**Status:** AUTHENTICATED + TESTED. Token valid for 5 days.

---

## Quick Start

```python
import httpx, json
from pathlib import Path

token = json.loads(Path("~/.her-os/annie/mcp-tokens/swiggy_tokens.json").expanduser().read_text())["access_token"]

headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json",
    "Accept": "application/json, text/event-stream",  # REQUIRED — server returns 406 without this
}

# Initialize session
init = {"jsonrpc": "2.0", "method": "initialize", "params": {
    "protocolVersion": "2025-03-26", "capabilities": {},
    "clientInfo": {"name": "annie", "version": "1.0"}
}, "id": 1}
resp = httpx.post("https://mcp.swiggy.com/food", json=init, headers=headers)

# Notify
httpx.post("https://mcp.swiggy.com/food", json={"jsonrpc": "2.0", "method": "notifications/initialized"}, headers=headers)

# Call any tool
req = {"jsonrpc": "2.0", "method": "tools/call", "params": {
    "name": "search_restaurants", "arguments": {"query": "masala chaat", "addressId": "d4r3nu7mqkqe26kcujt0"}
}, "id": 2}
resp = httpx.post("https://mcp.swiggy.com/food", json=req, headers=headers)
```

---

## Critical Gotcha: MCP SDK Transport DOES NOT WORK

**Both `sse_client` and `streamablehttp_client` from the MCP Python SDK fail with Swiggy.**

- `sse_client` → gets 401 before auth provider intercepts
- `streamablehttp_client` → hangs at `session.initialize()` indefinitely

**Root cause:** Swiggy requires `Accept: application/json, text/event-stream` header. The SDK doesn't send it. Server returns `406 Not Acceptable: Client must accept both application/json and text/event-stream`.

**Working approach:** Raw `httpx` POST with JSON-RPC payloads. The OAuth token acquisition via SDK's `OAuthClientProvider` works fine — only the transport is broken.

---

## Authentication

### OAuth Flow (one-time interactive)
1. Run `scripts/prototypes/swiggy_prototype.py auth`
2. Chrome opens with Swiggy login page
3. Log in with your Swiggy account
4. Browser redirects to localhost callback → token saved

### Token Details
- **Stored at:** `~/.her-os/annie/mcp-tokens/swiggy_tokens.json` (0o600 perms)
- **Client info at:** `~/.her-os/annie/mcp-tokens/swiggy_client.json`
- **Type:** Bearer (JWT)
- **Expires in:** 432,000 seconds = **5 days**
- **Refresh token:** NO — must re-auth every 5 days
- **Registered client_id:** `swiggy-mcp` (assigned by Swiggy, not configurable)

### OAuth Endpoints
- Discovery: `https://mcp.swiggy.com/.well-known/oauth-authorization-server`
- Authorization: `https://mcp.swiggy.com/auth/authorize`
- Token: `https://mcp.swiggy.com/auth/token`
- Registration: `https://mcp.swiggy.com/auth/register`
- Scopes: `mcp:tools`, `mcp:resources`, `mcp:prompts`
- PKCE: S256

### Gotcha: Discovery at Domain Root
OAuth discovery is at `https://mcp.swiggy.com/.well-known/oauth-authorization-server`, NOT at `https://mcp.swiggy.com/food/.well-known/...`. The SDK handles this correctly.

---

## Three Endpoints (Same Token)

| Endpoint | URL | Tools | Use Case |
|----------|-----|-------|----------|
| **Food** | `https://mcp.swiggy.com/food` | 16 | Restaurants, menus, food ordering |
| **Instamart** | `https://mcp.swiggy.com/im` | 13 | Groceries, order history, restock |
| **Dineout** | `https://mcp.swiggy.com/dineout` | ? | Table booking (not tested) |

All three use the **same OAuth token**. No separate auth needed.

---

## Food Endpoint — 16 Tools

| Tool | Type | Notes |
|------|------|-------|
| `get_addresses` | read | Returns all saved addresses. Field is `id` NOT `addressId`. |
| `create_address` | write | Needs lat/lng, full address, phone, type |
| `delete_address` | write | Delete saved address |
| `search_restaurants` | read | Search by cuisine/name/dish. Needs `addressId`. |
| `search_menu` | read | Search menu items across restaurants |
| `get_restaurant_menu` | read | Full menu for specific restaurant. Needs `restaurantId`. |
| `get_food_cart` | read | Current cart contents |
| `update_food_cart` | write | Add/modify items in cart |
| `flush_food_cart` | write | Clear entire cart |
| **`place_food_order`** | **DANGEROUS** | **Places real COD order. Real money.** |
| `fetch_food_coupons` | read | Available coupons/discounts |
| `apply_food_coupon` | write | Apply coupon to cart |
| `get_food_orders` | read | **Active orders ONLY** (not history) |
| `get_food_order_details` | read | Details for specific orderId |
| `track_food_order` | read | Live delivery tracking for orderId |
| `report_error` | write | Report MCP bugs to Swiggy |

### Food Endpoint Limitations
- **No order history** — `get_food_orders` ONLY returns active/in-progress orders. Even with `orderCount: 20`, returns empty when no delivery is in progress. Confirmed 2026-04-02. Past restaurant orders are ONLY accessible via the Swiggy app/website, not MCP. This is unlike Instamart which returns full history.
- **COD only** — `availablePaymentMethods: ["Cash"]` confirmed from live `get_food_cart` response (2026-04-02). No UPI, no card, no wallet via MCP. For online payment, must use the Swiggy app via ADB on Pixel.
- **No favorites/reorder** — must search + build cart from scratch
- **Workaround for restaurant history**: Use ADB on Pixel to read the Swiggy app's order history screen

### Cart Sync is ONE-WAY (Tested 3 Rounds, 2026-04-02)

| Direction | Syncs? | Evidence |
|-----------|--------|----------|
| **MCP → Pixel app** | **NO** | Added Bhel Puri via MCP, Pixel shows empty (tested with clean cart) |
| **Pixel app → MCP** | **YES** | Clearing items on Pixel cleared MCP cart |
| **Phone → MCP** | **YES** | Rajesh added item from phone, appeared in MCP |

**Conclusion:** MCP cart is write-only from MCP's perspective — the app never reads it. The app writes to a shared server that MCP can read. So:
- **MCP** = reading (search, menus, coupons, order history, browse)
- **ADB on Pixel** = writing (add to cart, payment, place order)
- **Optimal flow:** MCP searches and recommends → ADB adds items on Pixel → ADB navigates to payment → saved credit card → OTP via Telegram

### Price Format
**MCP prices are in RUPEES, not paise.** No conversion needed.
- Integer prices: `140` = ₹140 (e.g., Bhel Puri)
- Decimal prices: `319.05` = ₹319.05 (e.g., Kaju Burfi)
- DO NOT divide by 100 — this was a bug in the first prototype display code

---

## Instamart Endpoint — 13 Tools

| Tool | Type | Notes |
|------|------|-------|
| `get_addresses` | read | Same as food endpoint |
| `create_address` | write | Same as food endpoint |
| `delete_address` | write | Same as food endpoint |
| `search_products` | read | Search groceries by name/category/brand |
| **`your_go_to_items`** | **read** | **Frequently purchased items — perfect for restock suggestions** |
| `get_cart` | read | Current Instamart cart |
| `update_cart` | write | Add/modify grocery items |
| `clear_cart` | write | Clear cart |
| **`checkout`** | **DANGEROUS** | **Places real order. May support UPI (not just COD).** |
| **`get_orders`** | **read** | **FULL ORDER HISTORY — delivered orders with items, amounts, payment** |
| `get_order_details` | read | Beta-only (returns error for non-beta users) |
| `track_order` | read | Live tracking for orderId |
| `report_error` | write | Report bugs |

### Instamart is MORE Capable Than Food for Annie
- **Order history WORKS** (Food only shows active)
- **`your_go_to_items`** — Swiggy already tracks frequently purchased items
- **`checkout`** may support UPI payment (not just COD)
- Same OAuth token — no extra auth

### Instamart get_orders — Pagination
```python
arguments = {
    "addressId": "d4r3nu7mqkqe26kcujt0",  # optional — filter by address
    "count": 20,                             # max ~20 per call
    "fromTime": 1774905000000,               # Unix ms — fetch FROM this time backward
    "orderType": "DASH",                     # "DASH" works, "INSTAMART" returns empty
    "activeOnly": False                      # True = only in-progress
}
```
- **15-day lookback limit** from `fromTime`
- To get full history: chain calls, setting `fromTime` to oldest order's timestamp from previous batch
- Without `fromTime`: returns ~10 most recent orders

---

## Rajesh's Data (Verified)

### Delivery Address
- **Home (HSR Layout):** `id: d4r3nu7mqkqe26kcujt0`
- Address: 424, Sector 5, 7th Sector, HSR Layout, Bengaluru
- Phone: ****8911
- **5 other addresses** saved (Pune hotel, Roshan's teacher, Aarkit, Mysore, Chamarajpet)

### Recent Instamart Orders (39 orders, Feb 27 — Mar 30, 2026)
Rajesh orders groceries **every 1-2 days**. Common items:
- **Daily staples:** Heritage Full Cream Milk, Akshayakalpa Organic Set Curd, Robusta Banana
- **Organic produce:** Bhoomi Farms (tomato, potato, pumpkin, sapota, ladies finger, chow chow)
- **Protein:** Licious Classic Eggs, Sundrop Peanut Butter
- **Bread:** Suchali's Artisan Bakehouse Multigrain Sourdough
- **Drinks:** Paper Boat Zero Sugar Sparkling Water, Coca-Cola Diet Coke
- **Cooking:** MTR Dosa Batter, iD Idly & Dosa Batter, Tata Sampann Besan, curry leaves
- **Average order:** ₹400-700, delivered in 6-10 min
- **Payment:** UPIIntent (not COD)

### April 1 Order — Not Found
Rajesh reports an April 1 Instamart order that doesn't appear in MCP results. Possible causes:
- API lag (order too recent for backend sync)
- May have been a Food order (restaurant), not Instamart
- MCP API quirk — investigate next session

---

## MCP JSON-RPC Protocol Reference

### Session Lifecycle
```
1. POST initialize → get protocolVersion, serverInfo, capabilities
2. POST notifications/initialized → acknowledge
3. POST tools/list → get all available tools
4. POST tools/call → execute a tool
```

### Request Format
```json
{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
        "name": "search_restaurants",
        "arguments": {"query": "masala chaat", "addressId": "d4r3nu7mqkqe26kcujt0"}
    },
    "id": 3
}
```

### Required Headers (ALL requests)
```
Authorization: Bearer {token}
Content-Type: application/json
Accept: application/json, text/event-stream
```

### Session ID
Some responses include `Mcp-Session-Id` header. If present, include it in subsequent requests:
```
Mcp-Session-Id: {value}
```

---

## Files in This Repo

| File | What |
|------|------|
| `scripts/prototypes/swiggy_prototype.py` | OAuth + CLI (auth/discover/explore/order) — NOTE: CLI commands still use broken SDK transport, need rewrite to raw httpx |
| `scripts/prototypes/tests/test_swiggy_prototype.py` | 40 unit tests (FileTokenStorage, OAuth callback, tool blocklist) |
| `scripts/prototypes/reports/swiggy_capability_report.json` | Food endpoint capability report |
| `scripts/prototypes/reports/swiggy_instamart_capability_report.json` | Instamart capability report |
| `~/.her-os/annie/mcp-tokens/swiggy_tokens.json` | OAuth token (0o600, 5-day expiry) |
| `~/.her-os/annie/mcp-tokens/swiggy_client.json` | OAuth client registration |

---

## Decision: ADB Order History Scraping (2026-04-02)

**Problem:** MCP APIs have a 15-day window limit for order history. Food MCP has NO history at all. But the apps on the phone have FULL history going back months/years.

**Decision:** Install all food/grocery apps on Pixel 9a and use ADB + uiautomator2 to scrape the complete order history from each app. One-time extraction → permanent local JSONL dataset.

**Output format:**
```jsonl
{"date": "2026-03-11", "vendor": "swiggy_food", "restaurant": "Meghana Foods", "items": ["Chicken Dum Biryani"], "total": 359, "payment": "UPI"}
{"date": "2026-03-30", "vendor": "swiggy_instamart", "store": "Instamart", "items": ["Organic Tomato", "Eggs", "Milk"], "total": 258, "payment": "UPI"}
```

**What Annie gets from this:**
- Favorite restaurants + cuisines (ranked by frequency)
- Grocery restock timing (milk every X days, eggs every Y days)
- Spending patterns (avg per order, monthly total)
- Cuisine mood patterns (biryani on Fridays, light meals on weekdays)
- Preferred payment methods per vendor

**Why ADB over MCP for history:** MCP = 15-day window, active-only for food. App = years of data. Scrape once, mine forever. MCP stays as the real-time ordering channel.

---

## Next Steps

1. **Rewrite prototype to use raw httpx** (not broken MCP SDK transport)
2. **Call `your_go_to_items`** — get Rajesh's frequently purchased items
3. **Search for masala chaat** — test restaurant search + menu browsing
4. **Investigate April 1 order** — may need different fromTime or Food endpoint history
5. **Test Zepto MCP** — groceries with confirmed order history
6. **Test Zomato MCP** — blocked (no localhost redirect), time-box 2hr
7. **Install apps on Pixel** — Plan B for ADB automation
