hemmiles-mcp-server
# Hemmiles MCP Server
An unofficial, **read-only** [Model Context Protocol](https://modelcontextprotocol.io) server that lets MCP clients (Claude Desktop, MCP Inspector, etc.) query the public **[hemmiles.com](https://hemmiles.com)** website through structured tools.
> **Important — what Hemmiles actually is.** The site was inspected directly before any code was written. **Hemmiles is not an online store.** It is the marketing website for **Hemmiles, a B2B SaaS "field‑visit / field‑sales management" product** (visit planning, route planning, GPS check‑in, photos & notes, follow‑ups, manager dashboard, reports). There is **no product catalog, cart, checkout, stock, or customer reviews.** This server therefore exposes the site's *real* capabilities — content, features, pricing, resources, FAQs, and company/contact info — and does **not** fake e‑commerce actions that the site does not support. See [Tool mapping](#tool-mapping) and [Known limitations](#known-limitations).
---
## Table of contents
- [Verified site findings (Stage 1)](#verified-site-findings-stage-1)
- [Architecture](#architecture)
- [Tools](#tools)
- [Tool mapping](#tool-mapping) (vs. the original e‑commerce brief)
- [Install & build](#install--build)
- [Run](#run)
- [Connect to Claude Desktop](#connect-to-claude-desktop)
- [Configuration](#configuration)
- [Example conversations](#example-conversations)
- [Example tool calls](#example-tool-calls)
- [Testing](#testing)
- [Security & compliance](#security--compliance)
- [Known limitations](#known-limitations)
- [Troubleshooting](#troubleshooting)
- [Project structure](#project-structure)
---
## Verified site findings (Stage 1)
| Check | Result |
|---|---|
| `GET /products.json` (Shopify probe) | Next.js **404** (`__next_error__`, `/_next/static/…`) → **not Shopify / WooCommerce** |
| Platform | **Next.js (App Router)**, React Server Components streamed into **server‑rendered HTML** |
| `robots.txt` | `Allow: /`, **`Disallow: /api/` for all agents** → the server never requests `/api/` |
| `sitemap.xml` | Index → `pages.xml`, `markets.xml`, `resources.xml` (~31 canonical pages) |
| Root `/` | **HTTP 307** geo‑redirect to a locale (e.g. `/in`); redirects must be followed |
| Locales | `global, in, us, gb, ca, au, nz, ie, sg, ae, za` |
| Structured data | Rich **JSON‑LD** on every page: `SoftwareApplication` (name, `featureList`, `offers[]`), `Organization` (email, contact points), `WebPage`, `FAQPage`, `BreadcrumbList` |
| Pricing | Per user / month, **locale‑specific currency** (e.g. India ₹3,500 / US $39 Starter). Monthly + annual billing in `offers[].priceSpecification[]` |
| Reliability | Intermittent `ECONNRESET` (rate‑limiting) → retry/backoff + conservative throttling required |
Because content and pricing are delivered as JSON‑LD inside server‑rendered HTML, **plain `fetch` is sufficient — Playwright is not used.**
## Architecture
- **TypeScript + Node 18+**, ESM.
- **Official MCP SDK** (`@modelcontextprotocol/sdk`), tools registered with `registerTool` (Zod input + output schemas, annotations, `structuredContent`).
- **stdio** transport by default (for Claude Desktop); optional **Streamable HTTP** (`HEMMILES_TRANSPORT=http`).
- **`HemmilesClient`** — the single network gateway:
- SSRF‑safe: HTTPS only, host allowlisted to `hemmiles.com`, **every redirect hop re‑validated**, `/api/` refused.
- Resilient: per‑request timeout (AbortController), retry with exponential backoff on transient errors (ECONNRESET / 5xx / 429), manual redirect following.
- Conservative rate limiting (serialized requests with a configurable minimum interval) + in‑memory TTL cache.
- **`product-normalizer`** — pure, JSON‑LD‑first extraction (via `cheerio`), unit‑tested with fixtures.
- No secrets, no credentials, no writes, no transactions.
## Tools
All tools are read‑only (`readOnlyHint: true`) and support `response_format: "markdown" | "json"`. Locale defaults to the server's configured locale (`in` out of the box).
| Tool | Purpose |
|---|---|
| `hemmiles_list_pages` | Enumerate site pages by section (from the live sitemap). |
| `hemmiles_get_page` | Full normalized content of one page: title, description, headings, breadcrumbs, links, JSON‑LD types, body text. |
| `hemmiles_search_site` | Keyword search across pages (slug + deep full‑text), ranked, with snippets & pagination. |
| `hemmiles_list_features` | The product's capabilities from `SoftwareApplication.featureList`, linked to feature pages. |
| `hemmiles_get_pricing` | Subscription plans for a market: price, currency, unit, user limits, monthly/annual billing. |
| `hemmiles_list_resources` | Guides, blog posts, docs/help hubs. |
| `hemmiles_get_faqs` | FAQ Q&A from a page's `FAQPage` JSON‑LD (pricing page has a detailed set). |
| `hemmiles_get_company_info` | Organization data: name, site, logo, support email, contact points. |
| `hemmiles_get_policies` | Security / privacy / terms / legal / cookies pages that actually exist. |
| `hemmiles_get_contact_options` | Official demo / contact / sales links + support email (the safe alternative to cart/checkout). |
## Tool mapping
How the original e‑commerce brief maps onto this SaaS site:
| Brief tool | Status | Implemented as |
|---|---|---|
| `search_products` | ✅ remapped | `hemmiles_search_site` |
| `get_product_details` | ✅ remapped | `hemmiles_get_page` |
| `list_categories` | ✅ remapped | `hemmiles_list_pages` |
| `get_featured_products` | ✅ remapped | `hemmiles_list_features` |
| `get_product_price` | ✅ remapped | `hemmiles_get_pricing` |
| `get_store_information` | ✅ remapped | `hemmiles_get_company_info` |
| `get_return_policy` / `get_shipping_information` | ⚠️ partial | `hemmiles_get_policies` — **no shipping/returns exist for a digital SaaS**; billing/trial terms are in `hemmiles_get_faqs`. Documented, not faked. |
| `get_product_reviews` | ❌ not implemented | No public reviews/testimonials in the site's structured data. |
| `check_product_availability` | ❌ not implemented | No stock/inventory concept (SaaS). Plan availability is reflected in `hemmiles_get_pricing` (`availability` field). |
| `add_to_cart`, `get_cart`, `remove_from_cart`, `create_checkout_link` | ❌ not implemented | **No cart or checkout exists.** Use `hemmiles_get_contact_options` to reach the official demo/sales flow. The server never performs transactions or submits forms. |
## Install & build
Requires **Node.js ≥ 18**.
```bash
npm install
npm run build
```
This compiles TypeScript to `dist/`, with the entry point at `dist/index.js`.
## Run
```bash
# stdio (default)
npm start
# Streamable HTTP on :3000 (POST JSON-RPC to /mcp)
HEMMILES_TRANSPORT=http PORT=3000 npm start
# Development (auto-reload, no build needed)
npm run dev
# Explore interactively with the MCP Inspector
npm run inspector
```
Startup logs go to **stderr** only, so the stdio JSON‑RPC stream stays clean.
### HTTP endpoints
When running with `HEMMILES_TRANSPORT=http`, the following informational endpoints are served alongside MCP (which stays on `POST /mcp`):
| Method & path | Returns |
|---|---|
| `GET /` | HTML landing page (overview, endpoints, tool list, markets) — open it in a browser |
| `GET /health`, `GET /healthz` | Liveness JSON `{status:"ok", version, uptimeSeconds, …}` |
| `GET /version` | Version/protocol JSON |
| `GET /.well-known/mcp.json` | Discovery doc (transport, endpoint, tool names) |
| `POST /mcp` | MCP JSON-RPC (Streamable HTTP) |
```bash
curl http://localhost:3001/health
# {"status":"ok","name":"hemmiles-mcp-server","version":"1.0.0",
# "transport":"http","locale":"in","uptimeSeconds":6,"mcpEndpoint":"/mcp"}
```
Open `http://localhost:3001/` in a browser to see the landing page. (stdio transport has no HTTP endpoints; its health is simply that the process is running and `tools/list` responds.)
## Connect to Claude Desktop
1. `npm install && npm run build`.
2. Open Claude Desktop → **Settings → Developer → Edit Config** (this opens `claude_desktop_config.json`).
3. Add the server using the **absolute path to the compiled entry point**, then restart Claude Desktop.
**Windows** (this machine's path):
```json
{
"mcpServers": {
"hemmiles": {
"command": "node",
"args": ["C:\\Users\\Hp\\Desktop\\Hemmiles_MCP\\dist\\index.js"],
"env": {
"HEMMILES_DEFAULT_LOCALE": "in"
}
}
}
}
```
**macOS / Linux** (adjust the path):
```json
{
"mcpServers": {
"hemmiles": {
"command": "node",
"args": ["/absolute/path/to/Hemmiles_MCP/dist/index.js"],
"env": { "HEMMILES_DEFAULT_LOCALE": "us" }
}
}
}
```
After restarting, the 🔌 tools menu will list the `hemmiles_*` tools.
## Configuration
All environment variables are **optional** (no secrets required). See `.env.example`.
| Variable | Default | Meaning |
|---|---|---|
| `HEMMILES_TRANSPORT` | `stdio` | `stdio` or `http` |
| `PORT` | `3000` | HTTP transport port |
| `HEMMILES_DEFAULT_LOCALE` | `in` | Default market (`global,in,us,gb,ca,au,nz,ie,sg,ae,za`) |
| `HEMMILES_TIMEOUT_MS` | `20000` | Per‑request timeout (1000–60000) |
| `HEMMILES_MAX_RETRIES` | `3` | Retries for transient errors (0–5) |
| `HEMMILES_MIN_REQUEST_INTERVAL_MS` | `600` | Minimum spacing between requests |
| `HEMMILES_CACHE_TTL_SECONDS` | `300` | In‑memory cache TTL (0 disables) |
| `HEMMILES_USER_AGENT` | `hemmiles-mcp-server/1.0 …` | Outbound User‑Agent |
## Example conversations
**Discover and price:**
> **User:** What does Hemmiles do, and how much is it in India?
> **Claude:** *(calls `hemmiles_list_features` → Visit planning, Route planning, GPS check‑in, Photos & notes, Follow‑ups, Offline mode, Manager dashboard, Reports; then `hemmiles_get_pricing {locale:"in"}`)* Hemmiles is field‑visit software… India pricing is Starter **₹3,500**, Professional **₹4,500**, Enterprise **₹8,000** per user/month (annual billing is cheaper), with a 14‑day free trial.
**Research a capability:**
> **User:** Does it work without signal? Show me the details.
> **Claude:** *(`hemmiles_search_site {query:"offline"}` → `features/offline-mode`, then `hemmiles_get_page {path:"features/offline-mode"}`)* Yes — Offline mode lets reps keep working without signal and syncs later…
**Get in touch:**
> **User:** How do I get a demo?
> **Claude:** *(`hemmiles_get_contact_options`)* Book a demo at `https://hemmiles.com/in/demo`, or email `hello@hemmiles.com`. (The server only returns official links; it doesn't submit anything for you.)
## Example tool calls
MCP `tools/call` request and the resulting `structuredContent` (abridged, verified against the live site):
```jsonc
// hemmiles_get_pricing
{ "name": "hemmiles_get_pricing", "arguments": { "locale": "in", "response_format": "json" } }
// → structuredContent:
{
"url": "https://hemmiles.com/in/pricing",
"locale": "in",
"currency": "INR",
"plans": [
{ "name": "Starter plan", "price": "3500", "priceCurrency": "INR",
"unit": "per user per month", "minUsers": 3, "maxUsers": 10,
"availability": "https://schema.org/InStock",
"billing": [ { "name": "Monthly billing", "price": "3500" },
{ "name": "Annual billing", "price": "2900" } ] }
// … Professional, Enterprise
]
}
```
```jsonc
// hemmiles_search_site
{ "name": "hemmiles_search_site",
"arguments": { "query": "offline", "locale": "in", "limit": 3, "response_format": "json" } }
// → results[0]: { "url": ".../in/features/offline-mode", "section": "features",
// "score": 19, "title": "Offline mode for field teams in India", "snippet": "…" }
```
```jsonc
// hemmiles_get_company_info → { "name": "Hemmiles", "email": "hello@hemmiles.com",
// "url": "https://hemmiles.com", "contactPoints": [ { "contactType": "sales", "url": ".../demo" } ] }
```
## Testing
```bash
npm test # unit tests (no network): validation, SSRF, normalizer, formatting
npm run typecheck
```
The unit tests use HTML fixtures whose **structure** mirrors the live site (JSON‑LD with numeric prices, array `priceSpecification`, `FAQPage`, `Organization`) but with test values. SSRF/robots rejections are asserted to happen **before** any network call.
## Security & compliance
- **Read‑only.** No writes, purchases, sign‑ins, form submissions, or transactions.
- **robots.txt respected.** `/api/` is never requested.
- **SSRF‑hardened.** HTTPS + `hemmiles.com` only; every redirect hop re‑validated; arbitrary‑URL fetching is impossible.
- **No sensitive logging.** No credentials exist; request logs avoid bodies.
- **Rate‑limited & cached** to be a good citizen of the site.
- Unofficial and not affiliated with Hemmiles.
## Known limitations
- **Not an e‑commerce integration** — no products/cart/checkout/reviews/stock, because the site has none. See [Tool mapping](#tool-mapping).
- **Pricing** reflects the publicly displayed list price only (monthly + annual as published); negotiated/promo pricing isn't exposed.
- **Search** ranks by keyword relevance across titles/descriptions/slugs/body; deep full‑text fetching is bounded (`MAX_SEARCH_FETCH`) to limit load, so very long‑tail matches on huge sites could be capped (the site is small, ~31 canonical pages).
- **Content changes** on the live site can change returned text; schema types are stable but values are live.
- Relies on the site's JSON‑LD; if a page omits structured data, tools fall back to DOM parsing and may return less detail.
## Troubleshooting
| Symptom | Fix |
|---|---|
| `NETWORK` / `TIMEOUT` errors | The site rate‑limits and occasionally resets connections. Retry; the client already backs off. Raise `HEMMILES_TIMEOUT_MS` / `HEMMILES_MAX_RETRIES`. |
| `NOT_FOUND` | The path doesn't exist for that locale. Use `hemmiles_list_pages` / `hemmiles_search_site` to find valid paths, or try `locale:"global"`. |
| `DISALLOWED_PATH` | You asked for a robots‑blocked path (e.g. `/api/…`). This is intentional and cannot be overridden. |
| Server not visible in Claude Desktop | Ensure `npm run build` ran, the `args` path is **absolute** and uses escaped backslashes on Windows, then fully restart Claude Desktop. |
| Empty pricing plans | A locale page may lack offer JSON‑LD; the tool returns the page URL so you can view it directly. |
## Project structure
```
Hemmiles_MCP/
├── package.json
├── tsconfig.json
├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── src/
│ ├── index.ts # entry point + transport selection
│ ├── server.ts # wires config → client → tools
│ ├── config.ts # env configuration
│ ├── constants.ts # hosts, locales, page list, limits
│ ├── schemas/product.ts # Zod input + output schemas
│ ├── services/
│ │ ├── hemmiles-client.ts # SSRF-safe fetch, retry, rate limit, cache
│ │ ├── product-normalizer.ts # pure JSON-LD / HTML extraction
│ │ └── sitemap.ts # sitemap discovery + fallback
│ ├── tools/
│ │ ├── index.ts pages.ts search.ts features.ts
│ │ ├── pricing.ts resources.ts store.ts helpers.ts
│ └── utils/
│ ├── errors.ts validation.ts format.ts
└── tests/
├── fixtures.ts
├── validation.test.ts normalizer.test.ts
├── client.test.ts format.test.ts
```
## License
MIT — see [LICENSE](./LICENSE).
TDQS
Scored across 10 tools
Each tool maps to a distinct slice of the site (pages, search, features, pricing, resources, FAQs, policies, contact, company info), and descriptions explicitly cross-reference each other (e.g. get_page points to get_pricing/get_faqs). The only residual overlap is that get_page can return much of what get_pricing, get_faqs, and get_policies would, and get_company_info vs get_contact_options both surface contact data, but the guidance is clear enough that an agent can choose correctly.
All ten tools use the same prefixed verb_noun snake_case pattern (hemmiles_get_*, hemmiles_list_*, hemmiles_search_*), with get_ for single-resource fetch, list_ for collections, and search_ for queries used consistently and semantically. No mixed conventions or casing deviations.
Ten tools is well within the ideal 3-15 range and each corresponds to a real, non-redundant surface of a marketing/docs site. Nothing feels padded; each tool earns its place.
The surface covers discovery (list_pages, list_resources), retrieval (get_page), search, and the key content verticals (features, pricing, FAQs, policies, contact, company info), which is essentially full lifecycle for a read-only site server. Minor gaps: no way to enumerate integrations or teams/use-case pages as first-class lists, and no direct docs/help retrieval beyond get_page.