Skip to main content
Glama
Shopping MCP Server

Shopping MCP Server

by anshux1

Shopping MCP Server

A production-oriented shopping MCP server built with NitroStack. It exposes an eBay-backed product catalog, authenticated carts and orders, health resources, prompts, and seven static-export widgets.

place_order records an order in this service. It does not charge a payment method or create an eBay fulfillment order. Add a payment/fulfillment provider before using it for real commerce.

Features

  • eBay Browse search, item details, and Taxonomy category trees

  • Offline demo catalog for local development (EBAY_MOCK=true)

  • JWT authentication compatible with a Better Auth JWT plugin

  • User-scoped cart and order persistence

  • Postgres/Neon in production, atomic JSON-file storage locally, and :memory: test mode

  • Live price and availability verification during checkout

  • Expiring checkout quotes, order history, and cancellation

  • Zod input validation, normalized inputs, typed domain errors, structured responses, logging, caching, and eBay quota protection

  • Database, eBay, and system health checks exposed as health://checks

  • Product, cart, and order resources plus the shopping_assistant prompt

  • Product, search-results, category-tree, cart-summary, confirmation, order-summary, and cancellation widgets

Related MCP server: ingrammicro-mcp

Requirements

  • Node.js 22+

  • pnpm 11+

  • eBay application credentials for live catalog access (optional for demo mode)

  • Postgres/Neon DATABASE_URL for multi-instance production persistence

Quick start

pnpm install:all
cp .env.example .env
pnpm verify
pnpm dev

pnpm verify builds the server and widgets, starts an isolated stdio MCP server, and exercises tool schemas, resources, prompt execution, JWT authorization, catalog lookup, cart updates, checkout, order placement, history, cancellation, and cart isolation.

Configuration

Copy .env.example and set values for the deployment. Important settings:

Variable

Purpose

JWT_ALGORITHM / JWT_JWKS_URI

Explicit signing algorithm and Better Auth JWKS endpoint (EdDSA + /api/auth/jwks by default)

JWT_SECRET / JWT_SECRET_PREVIOUS

Current and temporary previous HMAC secrets; required only for HS* verification and at least 32 bytes outside development

JWT_AUDIENCE / JWT_ISSUER

Required JWT claim validation values (amazon-mcp / better-auth in the example)

JWT_EXPIRES_IN / JWT_MAX_TOKEN_LIFETIME_SECONDS

Issuer token lifetime and maximum accepted exp - iat lifetime (default 3600)

JWT_JWKS_CACHE_MAX_AGE_SECONDS

Maximum age of cached public keys before refresh (default 600)

DATABASE_URL

Postgres/Neon connection string; required outside development and takes precedence over file storage

DATABASE_FILE

Single-process local JSON store, or :memory: for tests; not a production fallback

EBAY_APP_ID / EBAY_CERT_ID

eBay keyset for live Browse/Taxonomy calls

EBAY_MARKETPLACE_ID

eBay marketplace, default EBAY_US

EBAY_MOCK

Set true to explicitly enable the deterministic offline catalog

MCP_TRANSPORT_TYPE

stdio, http, or dual; omitted development defaults to stdio, omitted production defaults to http

PORT / HOST

HTTP listener settings; production defaults to 3000 / 0.0.0.0 when omitted

ENABLE_CORS / CORS_ALLOWED_ORIGINS

CORS opt-in and exact comma-separated HTTP(S) origin allowlist; wildcard origins are rejected

SHOPPING_TAX_RATE

Decimal tax rate used in checkout, for example 0.0725

SHOPPING_FULFILLMENT_MODE

Must be set explicitly outside development; only demo is implemented and external refuses to start

SHOPPING_QUOTE_TTL_SECONDS

Checkout quote lifetime, default 600

SHOPPING_FEATURED_ITEM_IDS / SHOPPING_FEATURED_QUERY

Curated item list, or the fixed query used by shopping://featured-products

EBAY_MAX_RETRIES / EBAY_RETRY_BASE_MS

Bounded exponential backoff for transient eBay failures only

EBAY_CATEGORY_MAX_DEPTH / EBAY_CATEGORY_MAX_NODES

Bound a taxonomy response so a full tree cannot overflow a client

EBAY_QUOTA_FALLBACK

reject (default) or local when the shared quota counter is unreachable

TRUSTED_FORWARDED_HOSTS / REQUIRE_HTTPS

Reverse-proxy header policy; an X-Forwarded-Host outside the allowlist is rejected

HEALTH_DETAILS

Include storage mode and applied migrations in /readyz

OAUTH_ENABLED

Advertise OAuth 2.1 discovery metadata for external MCP clients

In development, missing eBay credentials are allowed for the offline demo. Outside development, set EBAY_MOCK=true to explicitly allow demo mode, or set EBAY_MOCK=false and provide both eBay credentials. A production configuration never silently falls back to the demo catalog. Monitor health://checks for the configured eBay status.

MCP surface

Tools

Tool

Auth

Description

search_products

Public

Search the eBay catalog

get_product

Public

Read current item details

get_categories

Public

Read the marketplace category tree or subtree

add_to_cart

JWT + shopping:write

Add or increment a user cart item

view_cart

JWT + shopping:read

Read the authenticated user cart

update_cart_item

JWT + shopping:write

Set quantity; 0 removes the item

checkout

JWT + shopping:write

Create a ten-minute quote with live price/availability checks

place_order

JWT + shopping:write

Persist an order from an unexpired quote and clear the cart

get_order

JWT + shopping:read

Read one order owned by the authenticated user

order_history

JWT + shopping:read

List the authenticated user’s orders

cancel_order

JWT + shopping:write

Cancel an order owned by the authenticated user

Protected calls use a Bearer token. For MCP clients, provide it as _meta.authorization: "Bearer <token>"; for Streamable HTTP clients, send the normal Authorization header. The verified JWT sub is the user ID—no tool accepts a caller-supplied userId. Tokens must contain a non-empty sub, iss, aud, and unexpired exp claim.

Authentication configuration

The selected strategy is a Better Auth JWT bridge for a trusted first-party frontend. The supported clients are that frontend and MCP clients that can forward bearer credentials. Better Auth must issue a signed JWT with a stable application-user sub claim. The recommended Better Auth JWT plugin configuration uses its JWKS endpoint and EdDSA; this server also supports explicitly configured RS*, PS*, ES*, and HS* algorithms. The server expects the following token contract from .env.example:

  • JWT_ALGORITHM=EdDSA and JWT_JWKS_URI=https://<frontend>/api/auth/jwks (recommended)

  • Or JWT_ALGORITHM=HS256 and JWT_SECRET when the issuer is explicitly configured to mint HMAC tokens

  • JWT_AUDIENCE=amazon-mcp

  • JWT_ISSUER=better-auth

  • JWT_EXPIRES_IN=1h (the issuer controls the actual exp claim)

The guard verifies the signature against the configured HMAC secret or cached remote JWKS, requires an explicit algorithm allowlist, validates expiration, issuer, audience, and non-empty sub, and fails closed when JWKS cannot validate a token. It accepts Authorization: Bearer <token> from HTTP requests and the equivalent MCP _meta.authorization metadata. Protected reads require shopping:read; cart, checkout, placement, and cancellation writes require shopping:write. A token with both scopes is needed for the complete recommended flow.

The corresponding Better Auth configuration should make these values explicit (the scopes should come from the application's authorization policy):

jwt({
  jwks: {
    keyPairConfig: { alg: 'EdDSA', crv: 'Ed25519' },
    rotationInterval: 60 * 60 * 24 * 30,
    gracePeriod: 60 * 60 * 24 * 30,
  },
  jwt: {
    issuer: process.env.JWT_ISSUER,
    audience: process.env.JWT_AUDIENCE,
    expirationTime: process.env.JWT_EXPIRES_IN,
    getSubject: (session) => session.user.id,
    definePayload: ({ user }) => ({
      sub: user.id,
      scopes: ['shopping:read', 'shopping:write'],
    }),
  },
})

Key rotation

For HMAC mode, JWT verification supports a current secret and one temporary previous secret. To rotate without logging out active users:

  1. Generate a new random secret and deploy it as JWT_SECRET.

  2. Move the old value to JWT_SECRET_PREVIOUS in the same deployment.

  3. Keep the previous value for at least the maximum token lifetime (and any clock-skew/rollout buffer).

  4. Remove JWT_SECRET_PREVIOUS in a later deployment after that window.

For the recommended JWKS mode, configure Better Auth's rotationInterval and gracePeriod; the server caches the JWKS, refreshes on key IDs, and never falls back to an old or unsigned key when verification fails. Never log secrets or tokens. A token signed by any other key/secret, with the wrong issuer/audience, malformed claims, wrong signature, or expired exp is rejected.

OAuth discovery is intentionally not enabled by default because this deployment targets a trusted first-party frontend. If external OAuth authorization-server clients are required, configure NitroStack's OAuth module and discovery endpoints before exposing the HTTP transport to them.

Recommended flow:

search_products → get_product → add_to_cart → view_cart
→ checkout → confirm quote → place_order → get_order

Resources and prompt

  • shopping://catalog-guide

  • shopping://featured-products

  • shopping://categories

  • shopping://cart-guide

  • shopping://order-guide

  • shopping://order-statuses

  • metrics://shopping

  • health://checks

  • widget://examples

  • shopping_assistant prompt for guided product research

Widget resources are automatically registered for every tool decorated with @Widget. pnpm build runs the Next.js static export and places the seven bundles in src/widgets/out/.

Commands

pnpm dev            # development stdio server
pnpm build          # widget export + TypeScript build
pnpm test           # build + unit tests + MCP protocol tests
pnpm test:unit      # unit and persistence tests only
pnpm test:coverage  # unit tests with coverage thresholds
pnpm test:postgres  # persistence tests against TEST_DATABASE_URL
pnpm verify         # build + lint + typecheck + unit tests + protocol tests
pnpm verify:mcp     # stdio, HTTP, dual, JWKS, and widget-mode protocol tests
pnpm verify:live    # live eBay checks; skips without EBAY_APP_ID/EBAY_CERT_ID
pnpm lint           # repository lint and formatting checks
pnpm typecheck      # TypeScript only
pnpm start          # build, then start
pnpm start:prod     # start an existing dist build

Postgres coverage is opt-in and truncates every shopping table, so point it only at a disposable database:

TEST_DATABASE_URL=postgresql://user@localhost:5432/amazon_mcp_test pnpm test:postgres

Correctness guarantees

  • Cart contents come from the server. add_to_cart accepts only item_id and quantity; the title, price, currency, URL, and availability are fetched from eBay. A forged price or a nonexistent item cannot enter a cart.

  • Cart writes are atomic. Every mutation is a locked read-modify-write (a per-user advisory lock plus row lock on Postgres, a serialized write queue for the file adapter), so concurrent add_to_cart calls cannot lose an update.

  • Quotes are durable and cart-bound. A checkout quote is persisted and fingerprinted against the cart it was priced from. If the cart changes afterwards, place_order returns a conflict and the newer cart is left intact.

  • Placement re-validates and is idempotent. Prices and availability are re-read immediately before placement, and the checkout_id acts as the idempotency key: a retry returns the original order (alreadyPlaced: true), and two replicas cannot both consume one quote.

  • The eBay budget is shared. With DATABASE_URL the daily application quota is an atomic Postgres counter that survives restarts and is shared by every replica. It counts eBay requests, not tool calls, because @Cache wraps @RateLimit.

  • Failures stay quiet. Upstream messages are redacted before they reach tool output, logs, or metrics, and unexpected errors are replaced with a generic message.

Observability

metrics://shopping reports tool invocations, failures and error codes, eBay request counts, latency, retries and failure categories, catalog cache hit rate, quota usage, storage mode, applied migrations, and evaluated alerts (sustained eBay failures, a low or exhausted daily budget, an unreachable quota counter). It contains names, counts, and durations only — never tokens, credentials, or shopper data.

Over HTTP, /healthz is liveness (process only, so a dependency outage never triggers a restart) and /readyz is readiness (fails when persistence is unusable; a degraded eBay dependency is reported but does not remove the replica from service). health://checks remains the MCP-facing view.

Persistence and deployment

The Postgres adapter applies versioned, forward-only migrations at startup (shopping_schema_migrations), each inside a transaction under an advisory lock so concurrent replicas cannot apply the same version twice. It creates shopping_carts, shopping_orders, shopping_quotes, and ebay_quota, and scopes every read by the authenticated user.

Postgres is required for more than one replica. Carts, checkout quotes, orders, and the eBay quota counter all live there. The JSON adapter keeps its state in one process and is for a single local/demo instance only.

Before deployment:

  1. Set NODE_ENV=production and explicitly choose MCP_TRANSPORT_TYPE=http (or dual only when stdio is intentionally needed).

  2. Set HOST=0.0.0.0, DATABASE_URL, TLS settings, JWT_SECRET, and live eBay credentials through the secret manager.

  3. Set SHOPPING_FULFILLMENT_MODE=demo explicitly; startup fails without it outside development.

  4. Keep EBAY_MOCK=false for live access and verify health://checks reports the database and eBay as up.

  5. Run pnpm verify, and pnpm verify:live once against the eBay sandbox with real credentials.

  6. Put the HTTP transport behind TLS. Set TRUSTED_FORWARDED_HOSTS to the public host and REQUIRE_HTTPS=true when a proxy terminates TLS. Leave CORS disabled for non-browser clients, or set ENABLE_CORS=true with an exact CORS_ALLOWED_ORIGINS allowlist; wildcard CORS is not supported.

  7. Load JWT_*, DATABASE_URL, and eBay credentials from the platform's secret store. Never commit a .env.

Logging and retention

Log records carry a request ID, tool name, authenticated subject, duration, and error code. Tool input is never logged, because it can contain a shipping address and _meta.authorization carries a bearer token; upstream eBay messages are redacted before they are logged. Treat the subject claim as personal data and keep operational logs to the shortest retention your incident process allows.

Not a real commerce backend

place_order records an order in this server's database. There is no payment authorization or capture, no inventory reservation, no eBay order or fulfilment integration, and no refund handling — the eBay Browse and Taxonomy APIs are read-only under an application token and cannot reserve stock. Orders therefore have exactly two states, placed and cancelled, any un-cancelled order is eligible for cancellation, and every order reports fulfillment: "demo". SHOPPING_FULFILLMENT_MODE=external is rejected at startup so a deployment cannot appear to be something it is not.

Available Tools

11 tools
add_to_cartAdd to cartC

Add a product to the authenticated user cart. Never pass a user ID; it comes from the JWT subject.

Inputs: item_id (required), quantity (required), title, currency, +4 more.

cancel_orderCancel orderA

Cancel an order belonging to the authenticated user when it has not already been cancelled.

Inputs: order_id (required).

checkoutPreview checkoutA

Create a short-lived checkout quote using live eBay prices and availability. This does not place an order.

Inputs: shipping_address.

get_categoriesGet product categoriesA

Retrieve the eBay category tree, or a category subtree when category_id is supplied.

Inputs: category_id.

get_orderGet orderB

Retrieve one order belonging to the authenticated user.

Inputs: order_id (required).

get_productGet productA

Retrieve current details and pricing for one eBay item.

Inputs: item_id (required).

order_historyOrder historyA

List orders belonging only to the authenticated user, newest first.

Inputs: limit, status.

place_orderPlace orderA

Place an authenticated user order from an unexpired checkout quote and clear the cart.

Inputs: checkout_id (required), shipping_address.

search_productsSearch productsB

Search the eBay catalog for products matching a text query and optional category.

Inputs: query (required), sort, limit, offset, +1 more.

update_cart_itemUpdate cart itemA

Set an item quantity in the authenticated user cart; quantity 0 removes it.

Inputs: item_id (required), quantity (required).

view_cartView cartA

View the authenticated user cart and its current item subtotal.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 11 tools

    Disambiguation5/5

    Each tool targets a distinct action or resource: search vs. specific product vs. category tree vs. cart operations vs. checkout vs. order lifecycle. No overlap in purpose, and descriptions clearly differentiate them.

    Naming Consistency3/5

    Most tools follow a verb_noun pattern (search_products, get_product, get_categories, view_cart, update_cart_item, add_to_cart, place_order, cancel_order), but 'checkout' and 'order_history' deviate (single verb, noun phrase without a verb). The inconsistency is noticeable but not chaotic.

    Tool Count5/5

    11 tools is well-scoped for a shopping domain, covering discovery, cart, checkout, and order management without unnecessary redundancy or bloat.

    Completeness4/5

    The tool surface covers the full shopping lifecycle: product discovery, cart, immediate cart actions, checkout, order placement, order retrieval, and cancellation. Minor gap: no direct 'get all categories' without subtree tree logic, but other methods query properly.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      C
      maintenance
      MCP server for eBay buyer-side workflows enabling search, watch, bid, buy, and management of MyeBay via a hybrid REST and Trading API stack.
      15
      1
      Apache 2.0
    • A
      license
      A
      quality
      B
      maintenance
      Enables the full Ingram Micro Reseller purchasing lifecycle through a single stateless HTTP MCP service, covering catalog and pricing, quotes, quote-to-order, order placement/modification/cancellation/lookup, invoices, renewals, special deals, returns, and freight estimates with one set of credentials.
      24
      Apache 2.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables searching active eBay listings, retrieving full item details, and browsing eBay category taxonomies through MCP tools.
      115 npm
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables searching eBay listings, inspecting item details, monitoring price drops, placing auction bids, and buying fixed-price items via guest checkout through eBay's Buy APIs.
      MIT