Skip to main content
Glama

NoonMCP

An MCP server exposing noon.com's product catalog — search, browse, product detail, price comparison and discount scanning — to any MCP client (Claude Code, Claude Desktop, or your own agent).

Defaults to the UAE storefront (prices in AED); all seven GCC/MENA storefronts are supported.


Tools

Tool

What it does

search_products

Keyword search, with price / brand / deal-tag filters and sorting

browse_category

List a category by its noon URL path

find_discounted_deals

Scan categories and rank by discount, with quality gates

get_product

Full detail for one SKU: every seller's offer, specs, highlights

compare_prices

Several SKUs side by side, ranked by discount

list_categories

Valid category slugs, deal-tag facets and sort fields

search / fetch

ChatGPT compatibility pair (see ChatGPT below)

Every product result carries price (was), sale_price (current), discount_percent, saving, rating, seller and a direct link.


Related MCP server: com.gladeapi/api

Transports

Command

Transport

For

python server.py

stdio

Claude Desktop / Claude Code

python server.py --http

Streamable HTTP on :8000/mcp

ChatGPT connectors, remote clients

python server.py --sse

SSE on :8000/sse

Older remote MCP clients

--host and --port override the bind address; NOON_TRANSPORT=http does the same via environment.

Setup

pip install -r requirements.txt
python server.py                  # stdio
python server.py --http --port 8000

ChatGPT

ChatGPT requires a remote server over HTTPS — it cannot reach localhost, so the server needs a tunnel or real hosting.

Its deep-research and company-knowledge paths also require two read-only tools named exactly search and fetch, returning a fixed compatibility schema (structuredContent plus the same JSON duplicated as text content). Both are implemented and verified over the wire — search returns {results: [{id, title, url}]} and fetch returns {id, title, text, url, metadata}.

The id is the noon SKU, so ChatGPT can search then fetch a full product record. The richer tools remain available to clients that allow arbitrary tools (Claude, and ChatGPT developer mode).

Add it in ChatGPT under Settings → Connectors → Add custom connector, using the /mcp URL.

Claude Code

claude mcp add noon -- python E:/Workspace/NoonMCP/server.py

Claude Desktop — claude_desktop_config.json

{
  "mcpServers": {
    "noon": {
      "command": "python",
      "args": ["E:/Workspace/NoonMCP/server.py"],
      "env": { "NOON_LOCALE": "en-ae" }
    }
  }
}

Environment (all transports)

Variable

Default

Notes

NOON_LOCALE

en-ae

en-ae, en-sa, en-om, en-eg, en-kw, en-bh, en-qa

NOON_MAX_RESULTS

50

Hard cap on products per response

NOON_HOST

0.0.0.0

Bind address for HTTP transports

NOON_PORT

8000

Port for HTTP transports

NOON_TRANSPORT

stdio

stdio, http or sse

LOG_LEVEL

WARNING

Logs go to stderr — stdout is the MCP transport

All seven are verified against the live site.

x-mp-country must match the market. Hardcoding ae appears to work for AE/OM/KW/BH/QA but makes SA and EG return zero hits with HTTP 200 — a silent empty result rather than an error. The client derives it from the locale.

The Saudi storefront HTML returns 403 to this client, so the session cannot be warmed there; warmup falls back to another storefront, which seeds the same Akamai cookies.


How noon.com actually works

Reverse-engineered by instrumenting the live storefront in Chrome DevTools. These findings are what the implementation is built on.

Endpoints

GET /_vs/nc/mp-customer-catalog-api/api/v3/search?q=<query>
GET /_vs/nc/mp-customer-catalog-api/api/v3/u/<category-path>/
GET /_vs/nc/mp-customer-catalog-api/api/v3/product/<sku>
GET /_vs/nc/mp-customer-catalog-api/api/v3/u/desktop-navigation/

<category-path> is exactly the path from the website URL, so /uae-en/beauty/makeup-16142/ becomes .../api/v3/u/beauty/makeup-16142/. Search and category listings share one schema: nbHits, nbPages, facets, hits[].

Akamai Bot Manager — the load-bearing constraint

noon.com sits behind Akamai. On /_vs/ paths:

  • requests, httpx, Node http2 and curl without nghttp2 all get their HTTP/2 stream reset with INTERNAL_ERROR — even carrying valid session cookies harvested from a real browser. The block keys on the TLS/HTTP2 fingerprint, not the session.

  • Requests issued from Chrome DevTools via CDP are blocked too, while the page's own XHRs succeed — so in-browser scraping is not a way around it.

The fix is curl_cffi with Chrome impersonation, reproducing Chrome's JA3/JA4 and HTTP/2 SETTINGS. Swapping it for requests breaks the server.

Two further gotchas:

  1. Warm up first. Fetch the storefront HTML once to seed the Akamai cookies (AKA_A2, ak_bmsc, bm_s, bm_so). The first /_vs/ call without this is reset.

  2. Send minimal headers. Only accept, x-locale, x-mp-country and referer. Adding the frontend's x-platform / x-content / x-cms makes search and listings reset — those belong only on desktop-navigation. Replaying the browser's full header set also fails, because the frontend emits x-ab-test: with an empty value and Envoy resets on it.

Pricing — easy to get backwards

{ "price": 56.92,        // the WAS / reference price
  "sale_price": 34.80 }  // the CURRENT selling price; null when not discounted

There is no discount_percent field; it is computed here.

On product detail the shape differs: the name is product_title, buying_options is empty, and pricing lives in variants[].offers[] — one entry per seller, each with its own price, sale_price, stock and store_name.

No discount sort

sort[by]=discount is silently ignored — the API falls back to popularity. Supported sorts: popularity, price, best_rating, new_arrivals. find_discounted_deals therefore pulls pages and ranks client-side, and the client raises rather than pretending a discount sort worked.

Limits

limit caps at ~200 and nbPages at 50, so any single query reaches at most ~10,000 products.

Deal facets

Live counts observed on the Oman storefront (Electronics):

f[deal_tag][]

Count

mega-deal

~72,900

deals-generic

~97,800

Clearances-deal-tag

~34,100

flash-sale-tag

~245


Why find_discounted_deals is more than a threshold

Measured on live data, not assumed:

  • A bare "40% off" filter matched 54% of products carrying the mega-deal tag, and the deepest "discounts" were filler — a 1.20 OMR pack of coat hangers marked down from 18.01. Hence min_price and min_saving.

  • noon lists every colourway as its own SKU, so one scan surfaced four Galaxy S25 Ultra colours and two identical roborock listings in a single top ten. Hence variant collapsing, which keeps the deepest discount per product.

A live 3-category scan: 600 products → 115 qualifying at ≥50% → 112 after collapsing, in 2.3s.

84.6%   6.42 (was 41.60)  save 35.18  Man Eau de Toilette 100ml
78.5%  15.01 (was 69.72)  save 54.71  Korean Brand Z8 Smart Watch
75.2%  19.52 (was 78.66)  save 59.14  16 Inch Stand Fan, 60W

Scanning is the slow part — each category/tag/page is one request — so keep pages_per_category small for interactive use.


Deploying as a remote server

Any Docker host works. Build and run:

docker build -t noon-mcp .
docker run -d --name noon-mcp --restart unless-stopped   -p 8000:8000 -e NOON_LOCALE=en-ae noon-mcp

Or with compose:

services:
  noon-mcp:
    build: .
    restart: unless-stopped
    ports: ["8000:8000"]
    environment:
      NOON_LOCALE: en-ae

The image defaults to NOON_TRANSPORT=http, so it serves Streamable HTTP on :8000/mcp. Its healthcheck performs a real initialize round-trip rather than a bare GET — a GET returns 406, because Streamable HTTP requires the client to accept both application/json and text/event-stream, and it would pass even if the MCP layer were broken.

ChatGPT needs HTTPS, so put it behind a reverse proxy or tunnel (Caddy, Traefik, nginx, Cloudflare Tunnel, ngrok) and point the connector at https://your-host/mcp.

The endpoint is unauthenticated. It is read-only against public noon data, but anyone with the URL can call it — add a proxy-level token or access policy if that matters to you.

Tests

python -m pytest tests/ -q      # 17 tests, no network required

SDK pin

requirements.txt pins mcp>=1.2,<2. The v2 SDK renamed FastMCP to MCPServer and changed the tool and transport APIs; this server targets the v1 API it was built and tested against. Without the pin a fresh install picks up 2.x and the server fails to import.

Notes

  • This reads the same public JSON the website serves to any visitor. Requests are throttled (0.3s) and retried with backoff; keep concurrency modest.

  • It depends on an internal, undocumented API. noon can change field names, paths or bot rules without notice. If everything starts returning errors, check whether the header set or impersonation profile still passes.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides read-only access to normalized Amazon marketplace data, including product details, search, offers, reviews, sellers, categories, deals, best sellers, identifiers, stock, and sales estimates across 13 marketplaces.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables Ozon marketplace customer-side operations including product search, detailed product cards, reviews, seller comparisons, and delivery city selection via MCP.
    4
    19
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables querying DNS Shop product data via MCP: search, product details, reviews, specifications, variants, categories, brand catalogs, availability by city, and store listings.
    12
    18
    MIT