shopping-mcp
Provides tools for searching and retrieving product information from connected Shopify stores, including filtering by price, merchant, and availability, comparing products, and checking checkout support.
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., "@shopping-mcpfind black backpacks under $100 that are in stock"
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.
Shopping MCP
Shopping with Agent lets a shopper ask their AI assistant to search and compare products across connected stores. Merchants connect a Shopify store; its catalog is synced into PostgreSQL and exposed to assistants through an MCP server. "Shopping MCP" is the protocol, config, and repo name.
/: shopper-facing home page with an example conversation and theshopping-mcp.jsondownload./docs: how to add Shopping MCP to an assistant./seller: merchants connect their Shopify store and see connection state, product count, and sync status, with a sync/retry button. "Disconnect" currently only signs the seller out; the store keeps syncing.
This is a development app: auth and data isolation are not ready for public deployment. Product context lives in PRODUCT.md; domain terms (Merchant, MerchantConnection, Store connection, …) in CONTEXT.md.
Local setup
Prerequisites: Node.js 22+, pnpm, PostgreSQL 17+, and a Shopify app for connecting stores (see Shopify app setup).
Create an empty
shopping-mcpdatabase.Install and configure:
pnpm install cp .env.example .envFill in
.env(see Configuration). At minimum:DATABASE_URL,SESSION_SECRET,CREDENTIALS_KEY, and theSHOPIFY_*values.Migrate and start:
pnpm run db:migrate pnpm dev
pnpm dev starts three processes: the Next.js UI at http://127.0.0.1:3000, the Nest API and MCP server at http://127.0.0.1:3001, and a worker that drains catalog sync every 10s. Start them separately with pnpm dev:web, pnpm dev:api, and pnpm dev:worker.
Then open http://127.0.0.1:3000/seller and connect a Shopify store.
Related MCP server: E-Commerce MCP Server
Configuration
All variables live in the root .env.
Variable | Required | Purpose |
| yes | PostgreSQL URL including the user, e.g. |
| yes | Signs seller session cookies. Changing it signs every seller out. |
| yes | Encrypts stored Shopify tokens. Must differ from |
| yes | Your Shopify app's client ID. |
| yes | Your Shopify app's client secret. Verifies OAuth callbacks (HMAC) and exchanges tokens. |
| yes | OAuth callback, |
| no | Requested scopes; default |
| no | API port; default |
| no | Web UI origin for CORS and the post-OAuth redirect; default |
| no | API origin the web UI calls; default |
Generate each secret separately, e.g. openssl rand -hex 32. Rotating SESSION_SECRET only signs sellers out. Changing CREDENTIALS_KEY makes stored Shopify tokens unreadable, and those stores must reconnect. Credentials encrypted before CREDENTIALS_KEY existed (with the old SESSION_SECRET-derived key) still decrypt and are re-encrypted on their next sync.
Shopify app setup
One Shopify app serves every merchant; merchants never configure anything. In your app's settings (Dev Dashboard at dev.shopify.com, or the Partner Dashboard for older apps):
Copy the client ID and secret into
SHOPIFY_API_KEYandSHOPIFY_API_SECRET.Add
SHOPIFY_REDIRECT_URIto the allowed redirect URLs, character for character (127.0.0.1≠localhost,http≠https, port and trailing slash matter). On the Dev Dashboard, settings live in a version: create and release it.Add one redirect URL per environment you run (local, staging, production), or use a separate app per environment.
Only stores the app's distribution allows can connect: custom distribution limits installs to specific stores; public distribution allows any store.
If the dashboard rejects an http:// URL, expose the API through an HTTPS tunnel (cloudflared tunnel --url http://127.0.0.1:3001 or ngrok http 3001) and use that URL for both the dashboard and SHOPIFY_REDIRECT_URI.
Commands
Command | Purpose |
| Web :3000, API :3001, and the sync worker |
| Apply outstanding SQL migrations transactionally |
| Same as |
| Workspace tests and architecture boundaries; no database |
| Real PostgreSQL tests; migrate first |
| TypeScript validation |
| ESLint across all workspaces |
| Prettier |
| Typecheck every workspace, then build the Next.js app |
ESLint checks code quality; Prettier handles formatting (eslint-config-prettier disables conflicting rules). Generated output and environment files are excluded from formatting. Never run local tooling against a production database.
MCP server
Endpoint: http://127.0.0.1:3001/api/mcp. GET /mcp.json downloads a shopping-mcp.json config pointing at it; add that to an assistant as a custom MCP server. Tools call catalog functions; they do not query SQL directly.
Tool | Backend | Purpose |
|
| Find products using keywords, price, merchant, and availability |
|
| Retrieve one product by its internal ID |
|
| Retrieve consistent details for 2–5 products so an assistant can explain differences |
|
| Look up a merchant checkout URL; currently returns |
Tool inputs use the catalog's own schemas from packages/contracts, so a tool never advertises input the catalog would reject. search_products takes real types: maxPrice is a number in major units (19.99 = $19.99), inStock a boolean. compare_products takes 2–5 distinct product IDs.
HTTP API
curl 'http://127.0.0.1:3001/api/products?q=black+backpack&maxPrice=100'
curl 'http://127.0.0.1:3001/api/products?inStock=true&limit=5&offset=0'
curl 'http://127.0.0.1:3001/api/merchants'GET /api/products accepts q (up to 200 characters), optional merchantId (UUID), currency (uppercase, default USD), maxPrice (non-negative number in major units), inStock (true/false), limit (1–100, default 24), and offset (0–100000). It returns { products, total, limit, offset }. Each product includes its merchant, stable internal ID, external ID, name, description, priceMinor, currency, images, inventory, product URL, and update time. Invalid filters return 400; database unavailability returns 503 without database details. The controller only converts query strings into the same typed search the MCP tool uses.
The API filters by currency; it does not convert currencies. Prices assume two-decimal currencies (see issue #7).
Architecture
apps/web (Next :3000) ──credentialed fetch──> apps/api (Nest :3001) ──> packages/commerce ──> PostgreSQL
↑
apps/worker (no HTTP) ── every 10s drainSyncRuns ── sync_runs outbox ─────────┘This is a pnpm workspace, not a distributed commerce backend. Business operations live in packages/commerce and are shared by the API and worker. Import its feature entry points (@shopping-mcp/commerce/catalog, /merchants, /auth, /sync, /store-connection, /connectors, and /connectors/shopify); there is no root barrel export.
packages/database: Drizzle schema, pool, migrations, and a test-only isolated-schema helper (@shopping-mcp/database/testing).packages/contracts: browser-safe schemas and JSON types shared by the API, MCP tools, and web client.packages/commerce: catalog, merchants, sessions, Store connection (Shopify OAuth), sync, and connectors.packages/config: server-only environment helpers.apps/api: Nest HTTP controllers, cookies, OAuth responses, and MCP tools.apps/worker: outbox drain loop.apps/web: shopper, docs, and seller UI only.
Data model
Merchant 1 — 1 MerchantConnection, Merchant 1 — N Product. Table shapes are declared in packages/database/src/schema.ts. SellerStatus and CatalogProduct are read models, not table rows.
A MerchantConnection stores the connector type, non-secret configuration, the shop domain, encrypted credentials with their expiry times, the enabled flag, last attempt/success times, and the last error. A Merchant is bound to one shop and never switches shops; a shop belongs to at most one Merchant. Products have a unique (merchant_id, external_id) constraint, so different stores may reuse the same SKU. Money uses integer minor units, not floating-point storage. Inventory is a non-negative integer; unavailable products stay searchable unless inStock=true.
Store connection
@shopping-mcp/commerce/store-connection owns the Shopify OAuth lifecycle behind two operations:
Begin: validate the
*.myshopify.comdomain, pick the signed-in Merchant or the one that owns (or will own) the shop, reject a shop owned by another Merchant (shop_taken) or a Merchant already bound to another shop (shop_mismatch), and store a single-use OAuth attempt tied to the browser by anoauth_bindingcookie (10 minutes).Complete: verify the callback HMAC, consume the attempt, exchange the code for tokens outside any DB transaction, then store encrypted credentials, request a sync, and create the seller session in one transaction.
Both return typed outcomes ({ ok: false, reason }); the API maps each reason to an HTTP status. A stale session cookie counts as signed out. A failed token exchange uses up the attempt; the seller starts again.
Sync semantics
Enqueue a
sync_runsrow (pending) on Store connection or seller retry. Dedup if pending/running already exists.The worker claims one run every 10s (
running, or stalerunningolder than 10 minutes), then:Acquire a PostgreSQL advisory lock for the connection; concurrent sync attempts fail fast.
The connector prepares its credentials. Shopify access tokens expire after an hour: within 5 minutes of expiry the connector refreshes them and stores the rotated tokens immediately, before fetching. A rejected refresh fails with "Shopify access expired. Reconnect your store."
Fetch the entire source snapshot, with a 10-second HTTP timeout.
Validate the entire normalized catalog, including unique external IDs, quantities, money, and HTTP(S) URLs, before modifying products.
In one database transaction, upsert products, reactivate returning items, mark missing items inactive, and record success.
On failure, roll back product writes, preserve the last good catalog and last successful sync time, and record the error. Release the lock in all paths.
Mark the run
succeededorfailed.
An explicitly complete empty snapshot deactivates all products for that merchant. A failed or incomplete fetch does not. Repeated syncs preserve product IDs; update timestamps represent the latest observation. Sync is full-snapshot only, capped at 10,000 products per merchant. Delta imports, variants, and multi-location inventory are intentionally deferred.
Search
PostgreSQL maintains an English tsvector from product name and description, indexed with GIN. websearch_to_tsquery supports word matching, phrases, and OR; relevance plus name and ID gives deterministic ordering. This is word-based search, not typo correction or substring matching. Price and stock filters run in SQL; maxPrice is compared with exact numeric math, so values like 19.999 behave correctly. Count and page are read from one repeatable-read snapshot so they stay consistent during sync.
Boundaries
Do not expose this development app to the public internet without auth. Connector responses are treated as untrusted data; only normalized, validated values reach storage. Shopify credentials are encrypted with CREDENTIALS_KEY (AES-256-GCM), never stored in connection config.
apps/web contains Next.js UI. packages/commerce and packages/database contain backend implementation. Timestamps in responses are ISO strings. Boundary tests check package subpaths, relative imports, and web aliases: shared packages cannot import apps, commerce cannot import HTTP/MCP frameworks, and web and contracts cannot import backend packages.
Tests
Tests live in their owning workspace's tests/ directory:
apps/api/tests: HTTP basics, session cookies, and the Store connection failure → status table.integration/: Shopify connect end to end over HTTP, and JSON-RPC calls to/api/mcpplus REST search.apps/web/tests: API URL helpers and seller response validation.apps/worker/tests: outbox scheduling.packages/commerce/tests: catalog helpers, Shopify product mapping, and snapshot validation.integration/: Store connection, Shopify token refresh, catalog lifecycle, and the sync outbox against PostgreSQL.packages/contracts/tests: search and product-ID schemas.packages/config/tests: environment helpers.Root
tests/: repository architecture boundaries only.
Run one workspace with pnpm --filter @shopping-mcp/api test or pnpm --filter @shopping-mcp/commerce test. Root pnpm test runs all database-free suites.
For integration tests, point DATABASE_URL at a disposable PostgreSQL database, run pnpm db:migrate, then pnpm test:integration. Workspace integration scripts load the root .env; an explicitly set DATABASE_URL takes precedence. Most integration tests run in a throwaway schema from createTestDatabase(); the catalog lifecycle and MCP HTTP tests use the migrated database and create uniquely named records. Shopify is never called: Store connection tests inject a fake token exchange, token-refresh tests stub fetch, and catalog and outbox tests use fake connectors.
The API and worker run TypeScript through tsx; they do not emit build artifacts. pnpm build checks all workspaces and builds the web app.
Troubleshooting
redirect_uri is not whitelistedfrom Shopify: addSHOPIFY_REDIRECT_URI, exactly, to the Shopify app's allowed redirect URLs and release the version (Shopify app setup)."Shopify access expired. Reconnect your store." on
/seller: the refresh token expired (90 days without a sync) or the app was uninstalled. Connect the same shop again.CREDENTIALS_KEY is required: set it in.env; see Configuration.Catalog setup screen / 503: check
.env, database health, andpnpm run db:migrate.Sync fetch failed: check the Shopify connection and scopes. The previous catalog remains intact.
No products: connect a Shopify store, then wait for the worker or use Retry sync on
/seller.Database connection refused: make sure
DATABASE_URLmatches your PostgreSQL host and port.Migration ledger errors on an old database: databases built by the old SQL runner must be recreated before
pnpm run db:migrate.
This server cannot be deployed
Maintenance
Related MCP Connectors
Unified MCP server for 70+ eCommerce platforms: products, orders, customers, and more.
Hosted MCP for e-commerce: live product catalog, stock, and pricing for AI agents.
Federated commerce search across independent WooCommerce merchants. Keyless, read-only MCP server.
Search multi-merchant supply, checkout, and track orders via MCP.
Related MCP Servers
- FlicenseAqualityDmaintenanceEnables product search functionality via MCP, allowing Claude to search products by keyword with filters, list categories, and retrieve product details.3-
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage products, shopping carts, and orders in an online store through a well-defined MCP API.-
- AlicenseNot gradedqualityBmaintenanceFederated commerce search MCP server enabling AI agents to query real product offers, prices, and availability across independent WooCommerce stores without API keys or registration.MIT
- FlicenseAqualityBmaintenanceEnables unified product search, coupon retrieval, and cross-platform price comparison across Taobao, JD, and Pinduoduo affiliate APIs through MCP.51-