products-mcp-server
README.md
# products-mcp-server
A custom [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server exposing **27 product tools** backed by [fakestoreapi.com](https://fakestoreapi.com).
It runs three ways from the same code:
- **Local (stdio)** — for Cursor / Claude Desktop, via `src/index.ts` → `dist/index.js`.
- **Render (persistent HTTP)** — a long-running Streamable HTTP server, via `src/http.ts` → `dist/http.js`.
- **Vercel (serverless HTTP)** — a stateless Streamable HTTP function, via `api/mcp.ts`.
## Architecture
```
src/
products-api.ts # Fake Store API client (endpoints + 60s TTL cache)
tools.ts # registerTools(server): all 27 tools (shared)
server.ts # createServer(): builds a configured server (shared)
index.ts # stdio entry point (local)
http.ts # persistent HTTP entry point (Render) — listens on $PORT
api/
mcp.ts # Vercel serverless entry point (stateless Streamable HTTP)
render.yaml # Render blueprint (web service)
vercel.json # Vercel function config
```
All entry points call `createServer()`, so the tool set stays identical across transports.
## Tools
| # | Tool | Description |
| --- | --- | --- |
| 1 | `getProducts` | All products (optional `limit`, `sort`). |
| 2 | `getProductById` | One product by `id`. |
| 3 | `getCategories` | List of categories. |
| 4 | `getProductsByCategory` | Products in a `category` (optional `limit`). |
| 5 | `searchProducts` | Keyword search over title + description. |
| 6 | `getProductsByPriceRange` | Products within `[min, max]`. |
| 7 | `getProductsAbovePrice` | Products costing more than `price`. |
| 8 | `getProductsBelowPrice` | Products costing less than `price`. |
| 9 | `getCheapestProduct` | Single cheapest product. |
| 10 | `getMostExpensiveProduct` | Single most expensive product. |
| 11 | `getTopRatedProducts` | Sorted by rating (optional `limit`). |
| 12 | `getMostReviewedProducts` | Sorted by review count (optional `limit`). |
| 13 | `getProductsByMinRating` | Products with rating >= `minRating`. |
| 14 | `getPriceStats` | count / min / max / average / median / total. |
| 15 | `getCategoryStats` | Per-category price stats. |
| 16 | `countProducts` | Total count (optional `category`). |
| 17 | `listProductTitles` | Lightweight id + title list. |
| 18 | `getRandomProduct` | A random product. |
| 19 | `compareProducts` | Compare 2–10 products by `ids`. |
| 20 | `getProductRecommendations` | Same-category picks for an `id`. |
| 21 | `getDiscountedPrice` | Price after `discountPercent`. |
| 22 | `summarizeCatalog` | High-level catalog summary. |
| 23 | `getCheapestInCategory` | Cheapest within a `category`. |
| 24 | `getHighestRatedInCategory` | Highest-rated within a `category`. |
| 25 | `createProduct` | Create a product (simulated by the API). |
| 26 | `updateProduct` | Update a product (simulated). |
| 27 | `deleteProduct` | Delete a product (simulated). |
> Note: fakestoreapi.com simulates write operations (create/update/delete) and does not persist them.
## Local development
```bash
npm install
npm run build # tsc: src -> dist
npm run typecheck # type-checks src + api
npm start # runs the stdio server
```
### Use locally in Cursor (`~/.cursor/mcp.json`)
```json
{
"mcpServers": {
"products": {
"command": "node",
"args": ["c:/Users/serch/OneDrive/Desktop/MCP-Servers/products-mcp-server/dist/index.js"]
}
}
}
```
Re-run `npm run build` after changing the TypeScript, since Cursor runs the compiled `dist/index.js`.
To run the HTTP server locally (same one Render uses):
```bash
npm run build
npm run start:http # listens on http://localhost:3000/mcp
# health check: curl http://localhost:3000/healthz
```
## Deploy to Render
Render runs a **persistent Node process** (via `src/http.ts`), which suits MCP well and avoids serverless cold starts / timeouts.
### Option A — Blueprint (recommended)
The repo includes `render.yaml`:
```yaml
services:
- type: web
name: products-mcp-server
runtime: node
plan: free
buildCommand: npm install && npm run build
startCommand: npm run start:http
healthCheckPath: /healthz
```
1. Push this folder to a Git repo.
2. In Render: **New → Blueprint**, select the repo. Render reads `render.yaml` and creates the web service.
3. Deploy. Your endpoint will be:
```
https://<your-service>.onrender.com/mcp
```
### Option B — Manual web service
**New → Web Service** → connect the repo, then set:
- **Runtime**: Node
- **Build Command**: `npm install && npm run build`
- **Start Command**: `npm run start:http`
- **Health Check Path**: `/healthz`
### Connect a client to the Render server
```json
{
"mcpServers": {
"products": { "url": "https://<your-service>.onrender.com/mcp" }
}
}
```
For stdio-only clients, bridge with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote):
```json
{
"mcpServers": {
"products": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://<your-service>.onrender.com/mcp"]
}
}
}
```
### Notes
- The server binds to `process.env.PORT` (Render sets this automatically). The MCP path defaults to `/mcp` and can be overridden with the `MCP_PATH` env var.
- Render's **free** web services sleep after inactivity; the first request after idle may be slow to wake.
- The deployed URL is public — add authentication before exposing sensitive tools.
## Deploy to Vercel
The `api/mcp.ts` function serves the MCP server over **Streamable HTTP** in **stateless** mode (a fresh server per request — no Redis required).
1. Push this folder to a Git repo and import it in Vercel (or run `npx vercel`).
2. Vercel compiles `api/mcp.ts` automatically; no build step is required for the function.
3. After deploy, your endpoint is:
```
https://<your-project>.vercel.app/api/mcp
```
### Connect a client to the deployed server
If the client supports Streamable HTTP (e.g. recent Cursor):
```json
{
"mcpServers": {
"products": { "url": "https://<your-project>.vercel.app/api/mcp" }
}
}
```
For stdio-only clients, bridge with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote):
```json
{
"mcpServers": {
"products": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://<your-project>.vercel.app/api/mcp"]
}
}
}
```
### Notes & limits
- **Stateless**: each request is self-contained. If you later need SSE streaming / persistent sessions across instances, add Upstash Redis and enable Vercel Fluid Compute.
- **Timeout**: `vercel.json` sets `maxDuration: 60`; each tool also has a 10s upstream-fetch abort guard.
- **Auth**: the deployed URL is public. Add authentication before exposing sensitive tools.
TDQS
B3.2/5.0
Scored across 27 tools
Disambiguation4/5
Most tools have distinct purposes, but some overlap exists, e.g., getTopRatedProducts vs getMostReviewedProducts vs getProductsByMinRating, and multiple price-filtering tools could cause confusion.
Naming Consistency5/5
All tool names follow a consistent camelCase verb_noun pattern (e.g., getProductById, createProduct, countProducts), with no mixing of conventions.
Tool Count3/5
27 tools is on the high side; many are specialized queries that could be combined with optional parameters, but each tool serves a clear purpose.
Completeness4/5
Covers CRUD and a wide range of queries, though lacks a generic sort/filter endpoint and bulk operations; write operations are noted as simulated.
Maintenance
ActivityInactive
ResponsivenessNo issues