@sitesreviews/mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] - 2026-06-28
11
+
12
+ First public release of the **Sites.Reviews MCP server** — a
13
+ [Model Context Protocol](https://modelcontextprotocol.io) server that gives any
14
+ AI assistant a website's trust score, real user reviews, and scam-check signals
15
+ from [Sites.Reviews](https://sites.reviews).
16
+
17
+ ### Added
18
+
19
+ - `check_domain` tool — returns the trust score (0–5), review count, and a
20
+ one-line verdict for any domain or URL.
21
+ - `get_reviews` tool — returns up to 20 recent reviews (author, rating, date,
22
+ title, body) with a configurable `limit`.
23
+ - Input normalisation for bare domains and full URLs, with automatic
24
+ `www.`-toggle retry on a miss.
25
+ - Human-readable text output plus a compact JSON summary and a source link back
26
+ to Sites.Reviews for citation.
27
+ - Live smoke test (`npm test`) that validates parsing against the real API.
28
+ - Multi-client install docs and example configs for Claude Desktop, Claude Code,
29
+ Cursor, Windsurf, and VS Code (Cline / Continue).
30
+ - CI matrix (Node 18 / 20 / 22), community-health files, and contribution guides.
31
+
32
+ ### Changed
33
+
34
+ - **Data source switched to the public Sites.Reviews REST API**
35
+ (`GET /api/public/v1/business/{domain}` and `/reviews/{domain}`) instead of
36
+ scraping schema.org JSON-LD from business pages. The API is stable, structured,
37
+ and read-only — no auth, no secrets, no writes.
38
+
39
+ [Unreleased]: https://github.com/SitesReviewsTrust/sites-reviews-mcp/compare/v1.0.0...HEAD
40
+ [1.0.0]: https://github.com/SitesReviewsTrust/sites-reviews-mcp/releases/tag/v1.0.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sites.Reviews
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ <div align="center">
2
+
3
+ # 🛡️ Sites.Reviews MCP server
4
+
5
+ **Give any AI assistant a website's trust score, real reviews and scam-check signals — before you trust or pay it.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@sitesreviews/mcp?logo=npm&color=cb3837)](https://www.npmjs.com/package/@sitesreviews/mcp)
8
+ [![CI](https://github.com/SitesReviewsTrust/sites-reviews-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/SitesReviewsTrust/sites-reviews-mcp/actions/workflows/ci.yml)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
10
+ [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-blueviolet)](https://modelcontextprotocol.io)
11
+ [![Node](https://img.shields.io/badge/Node-%E2%89%A518-43853d?logo=node.js&logoColor=white)](https://nodejs.org)
12
+
13
+ A [Model Context Protocol](https://modelcontextprotocol.io) server backed by [**Sites.Reviews**](https://sites.reviews) — the independent catalog of company & website reviews. Connect it once, then just *ask*.
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ ## ✨ What you can ask
20
+
21
+ Once connected, talk to your assistant in plain language — it picks the right tool automatically:
22
+
23
+ > 💬 **You:** *Is **ozon.ru** safe to buy from?*
24
+ >
25
+ > 🤖 **Assistant:** *Ozon has a **good reputation** on Sites.Reviews — a trust score of **4.2/5** across 128 reviews. Buyers generally report reliable delivery and working refunds. Source: https://sites.reviews/businesses/ozon.ru*
26
+
27
+ More things people ask:
28
+
29
+ - *"Check **bitmex.com** before I sign up."*
30
+ - *"What's the trust score for **1ps.ru**?"*
31
+ - *"Show me recent reviews of **aliexpress.com** — what do people complain about?"*
32
+ - *"Is this a scam: `https://some-shop.example/checkout`?"*
33
+ - *"I'm choosing between two marketplaces — which is more trusted?"*
34
+
35
+ The assistant calls Sites.Reviews, gets a trust score (0–5), a one-line verdict and real reviews, and **cites the source** so you can verify.
36
+
37
+ ➡️ See [`examples/USAGE.md`](./examples/USAGE.md) for full prompt → tool → answer walkthroughs.
38
+
39
+ ---
40
+
41
+ ## 🚀 Install anywhere
42
+
43
+ No clone, no global install needed — every client below runs the server on demand with `npx -y @sitesreviews/mcp`. It's a tiny, read-only server: **no API key, no auth, no secrets.**
44
+
45
+ <details open>
46
+ <summary><b>Claude Desktop</b></summary>
47
+
48
+ Edit `claude_desktop_config.json`:
49
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
50
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "sites-reviews": {
56
+ "command": "npx",
57
+ "args": ["-y", "@sitesreviews/mcp"]
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ Restart Claude Desktop — the **sites-reviews** tools appear in the tool menu.
64
+ File: [`examples/claude_desktop_config.json`](./examples/claude_desktop_config.json).
65
+ </details>
66
+
67
+ <details>
68
+ <summary><b>Claude Code</b></summary>
69
+
70
+ One command:
71
+
72
+ ```bash
73
+ claude mcp add sites-reviews -- npx -y @sitesreviews/mcp
74
+ ```
75
+
76
+ Or add it to `.mcp.json` (project) / `~/.claude.json` (user) manually:
77
+
78
+ ```json
79
+ {
80
+ "mcpServers": {
81
+ "sites-reviews": {
82
+ "command": "npx",
83
+ "args": ["-y", "@sitesreviews/mcp"]
84
+ }
85
+ }
86
+ }
87
+ ```
88
+ </details>
89
+
90
+ <details>
91
+ <summary><b>Cursor</b></summary>
92
+
93
+ Create `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` globally):
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "sites-reviews": {
99
+ "command": "npx",
100
+ "args": ["-y", "@sitesreviews/mcp"]
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ Then enable **sites-reviews** under *Settings → MCP*.
107
+ File: [`examples/cursor_config.json`](./examples/cursor_config.json).
108
+ </details>
109
+
110
+ <details>
111
+ <summary><b>Windsurf</b></summary>
112
+
113
+ Edit `~/.codeium/windsurf/mcp_config.json`:
114
+
115
+ ```json
116
+ {
117
+ "mcpServers": {
118
+ "sites-reviews": {
119
+ "command": "npx",
120
+ "args": ["-y", "@sitesreviews/mcp"]
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ Then click **Refresh** in the Windsurf MCP panel (Cascade).
127
+ </details>
128
+
129
+ <details>
130
+ <summary><b>VS Code — Cline / Continue</b></summary>
131
+
132
+ **VS Code (native MCP)** — create `.vscode/mcp.json`:
133
+
134
+ ```json
135
+ {
136
+ "servers": {
137
+ "sites-reviews": {
138
+ "type": "stdio",
139
+ "command": "npx",
140
+ "args": ["-y", "@sitesreviews/mcp"]
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ **Cline** — add to its MCP settings (`cline_mcp_settings.json`):
147
+
148
+ ```json
149
+ {
150
+ "mcpServers": {
151
+ "sites-reviews": {
152
+ "command": "npx",
153
+ "args": ["-y", "@sitesreviews/mcp"]
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ **Continue** — same `mcpServers` block in your Continue config.
160
+ File: [`examples/vscode_mcp.json`](./examples/vscode_mcp.json).
161
+ </details>
162
+
163
+ > 💡 The config is identical for almost every MCP client: a `command` of `npx` with args `["-y", "@sitesreviews/mcp"]`. Prefer a pinned global install? `npm install -g @sitesreviews/mcp` and point `command` at `sites-reviews-mcp`.
164
+
165
+ ---
166
+
167
+ ## 🧰 Tools
168
+
169
+ | Tool | Input | Returns |
170
+ | --- | --- | --- |
171
+ | **`check_domain`** | `domain` *(string — bare domain or full URL)* | `found`, `name`, `url`, `trustScore` (0–5), `reviewCount`, a one-line `verdict`, and the `source` URL. If the company isn't in the catalog → `found: false` with a helpful message. |
172
+ | **`get_reviews`** | `domain` *(string)*, `limit` *(integer 1–20, default 5)* | Up to `limit` recent reviews — `author`, `rating`, `date`, `title`, `body` (truncated to ~400 chars) — plus the trust score and source URL. |
173
+
174
+ Both tools accept a **bare domain** (`ozon.ru`) or a **full URL** (`https://ozon.ru/path?x=1`): the input is normalised automatically, and on a miss it retries with/without a leading `www.`.
175
+
176
+ Every response includes a readable text block, a compact **JSON summary**, and a link back to https://sites.reviews so the assistant can cite it.
177
+
178
+ <details>
179
+ <summary>Sample <code>check_domain</code> output</summary>
180
+
181
+ ````text
182
+ 🏢 1PS.RU (1ps.ru)
183
+ Trust score: 4.8/5 ★★★★★ · 34 reviews
184
+ Verdict: Excellent reputation (4.8/5 from 34 reviews).
185
+ Page: https://sites.reviews/businesses/1ps.ru
186
+
187
+ ```json
188
+ {
189
+ "found": true,
190
+ "domain": "1ps.ru",
191
+ "name": "1PS.RU",
192
+ "trustScore": 4.8,
193
+ "reviewCount": 34,
194
+ "verdict": "Excellent reputation (4.8/5 from 34 reviews).",
195
+ "source": "https://sites.reviews/businesses/1ps.ru"
196
+ }
197
+ ```
198
+ Source: Sites.Reviews (https://sites.reviews) — independent website & company review catalog.
199
+ ````
200
+ </details>
201
+
202
+ ---
203
+
204
+ ## ⚙️ How it works
205
+
206
+ - **Source:** the public Sites.Reviews REST API — `GET /api/public/v1/business/{domain}` and `/reviews/{domain}` (the same data Sites.Reviews also publishes as schema.org JSON-LD on each business page).
207
+ - **Read-only & anonymous.** No API key, no auth, no secrets, no writes — just polite, rate-limited HTTP GETs.
208
+ - **Local & private.** The server runs on your machine over stdio under your AI client. It opens no listeners and stores nothing.
209
+ - **Per-domain lookups.** Precise lookups by domain; a domain that isn't in the catalog returns `found: false` (not an error), with a link to be the first to review it.
210
+
211
+ ```
212
+ AI client ──stdio──▶ sites-reviews-mcp ──HTTPS GET──▶ sites.reviews public API
213
+ (Claude, (this server) (trust score + reviews)
214
+ Cursor, …)
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 🩺 Troubleshooting
220
+
221
+ | Symptom | Fix |
222
+ | --- | --- |
223
+ | **Tools don't appear** | Fully **restart** the client after editing its config (Claude Desktop especially). Confirm the JSON is valid (no trailing commas). |
224
+ | **`npx: command not found`** | Install Node.js ≥ 18 from [nodejs.org](https://nodejs.org). Check with `node --version`. |
225
+ | **First call is slow** | The first `npx -y` run downloads the package; subsequent runs are cached and fast. For zero-latency startup, `npm install -g @sitesreviews/mcp` and use `command: "sites-reviews-mcp"`. |
226
+ | **`found: false` for a real site** | The site may not be in the catalog yet, or the domain differs (try the apex vs `www.`, e.g. `ozon.ru` vs `www.ozon.ru`). The server already retries the `www.` toggle automatically. |
227
+ | **Corporate proxy / firewall** | The server needs outbound HTTPS to `sites.reviews`. Allow it, or set the standard `HTTPS_PROXY` env var for the client process. |
228
+ | **Want to see raw logs** | The server logs to **stderr** (stdout is reserved for the MCP protocol). Check your client's MCP log panel. |
229
+ | **Verify it runs at all** | `npx -y @modelcontextprotocol/inspector npx -y @sitesreviews/mcp` opens the MCP Inspector to exercise the tools by hand. |
230
+
231
+ ---
232
+
233
+ ## 🛠️ Develop
234
+
235
+ Requires **Node.js ≥ 18**.
236
+
237
+ ```bash
238
+ git clone https://github.com/SitesReviewsTrust/sites-reviews-mcp.git
239
+ cd sites-reviews-mcp
240
+ npm install
241
+ npm run build # compile TypeScript -> dist/
242
+ npm start # run the server over stdio
243
+ npm test # live smoke test against sites.reviews
244
+ ```
245
+
246
+ - `src/sites-reviews.ts` — public-API client + normalisation (pure, unit-testable).
247
+ - `src/index.ts` — MCP server wiring (`McpServer` + `registerTool`, stdio transport).
248
+
249
+ Contributions welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md), [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md), and [SECURITY.md](./SECURITY.md) for private vulnerability reporting. Changes are tracked in [CHANGELOG.md](./CHANGELOG.md).
250
+
251
+ ---
252
+
253
+ ## 🔗 Sites.Reviews ecosystem
254
+ - 🌐 **Website** — https://sites.reviews
255
+ - 🔍 **Trust score / scam-check** — search any domain on [sites.reviews](https://sites.reviews)
256
+ - 🤖 **Telegram bot** — [@SitesReviews_bot](https://t.me/SitesReviews_bot)
257
+ - 🧩 **Browser extension** — [sites.reviews/extension](https://sites.reviews/extension) · [repo](https://github.com/SitesReviewsTrust/sites-reviews-extension)
258
+ - 📚 **Docs** — [sites-reviews-docs](https://github.com/SitesReviewsTrust/sites-reviews-docs)
259
+ - 🔌 **API & widgets** — [sites-reviews-api](https://github.com/SitesReviewsTrust/sites-reviews-api)
260
+ - 🧠 **MCP server** — [sites-reviews-mcp](https://github.com/SitesReviewsTrust/sites-reviews-mcp)
261
+ - 🏛 **All repositories** — https://github.com/orgs/SitesReviewsTrust/repositories
262
+
263
+ ---
264
+
265
+ ## License
266
+
267
+ [MIT](./LICENSE) © 2026 Sites.Reviews
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sites.Reviews MCP server.
4
+ *
5
+ * Exposes two read-only tools that let an AI assistant check a website's trust
6
+ * score and read real reviews from https://sites.reviews before the user
7
+ * trusts or pays it.
8
+ *
9
+ * Transport: stdio. No auth, no secrets, no writes.
10
+ */
11
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sites.Reviews MCP server.
4
+ *
5
+ * Exposes two read-only tools that let an AI assistant check a website's trust
6
+ * score and read real reviews from https://sites.reviews before the user
7
+ * trusts or pays it.
8
+ *
9
+ * Transport: stdio. No auth, no secrets, no writes.
10
+ */
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ // Import from `zod/v3` (not the bare `zod` entrypoint) to match the type
14
+ // identity the MCP SDK's zod-compat layer references. Using the bare entry
15
+ // with zod 3.25 triggers TS2589 "type instantiation excessively deep".
16
+ import { z } from "zod/v3";
17
+ import { checkDomain, SITE_BASE, } from "./sites-reviews.js";
18
+ const SITE_LINK = `Source: Sites.Reviews (${SITE_BASE}) — independent website & company review catalog.`;
19
+ function ratingStars(score) {
20
+ if (score === undefined)
21
+ return "";
22
+ const full = Math.round(score);
23
+ return "★".repeat(full) + "☆".repeat(Math.max(0, 5 - full));
24
+ }
25
+ /** Render check_domain result as readable text + a compact JSON summary. */
26
+ function renderCheck(result) {
27
+ const lines = [];
28
+ if (!result.found) {
29
+ lines.push(`❓ Not found: ${result.domain}`);
30
+ if (result.message)
31
+ lines.push(result.message);
32
+ }
33
+ else {
34
+ lines.push(`🏢 ${result.name} (${result.domain})`);
35
+ lines.push(`Trust score: ${result.trustScore?.toFixed(1) ?? "n/a"}/5 ${ratingStars(result.trustScore)} · ${result.reviewCount ?? 0} reviews`);
36
+ if (result.verdict)
37
+ lines.push(`Verdict: ${result.verdict}`);
38
+ if (result.source)
39
+ lines.push(`Page: ${result.source}`);
40
+ }
41
+ const summary = {
42
+ found: result.found,
43
+ domain: result.domain,
44
+ name: result.name,
45
+ url: result.url,
46
+ trustScore: result.trustScore,
47
+ reviewCount: result.reviewCount,
48
+ verdict: result.verdict,
49
+ source: result.source,
50
+ };
51
+ lines.push("");
52
+ lines.push("```json");
53
+ lines.push(JSON.stringify(summary, null, 2));
54
+ lines.push("```");
55
+ lines.push(SITE_LINK);
56
+ return lines.join("\n");
57
+ }
58
+ function renderReview(r, idx) {
59
+ const parts = [];
60
+ const stars = r.rating !== null ? `${r.rating}/5 ${ratingStars(r.rating)}` : "no rating";
61
+ parts.push(`${idx}. ${r.title ?? "(untitled)"} — ${stars}`);
62
+ parts.push(` by ${r.author}${r.date ? ` on ${r.date}` : ""}`);
63
+ if (r.body)
64
+ parts.push(` ${r.body}`);
65
+ if (r.positives.length)
66
+ parts.push(` 👍 ${r.positives.join(", ")}`);
67
+ return parts.join("\n");
68
+ }
69
+ /** Render get_reviews result as readable text + a compact JSON summary. */
70
+ function renderReviews(result, limit) {
71
+ const lines = [];
72
+ if (!result.found) {
73
+ lines.push(`❓ Not found: ${result.domain}`);
74
+ if (result.message)
75
+ lines.push(result.message);
76
+ lines.push("");
77
+ lines.push(SITE_LINK);
78
+ return lines.join("\n");
79
+ }
80
+ const reviews = (result.reviews ?? []).slice(0, limit);
81
+ lines.push(`📝 Reviews for ${result.name} (${result.domain})`);
82
+ lines.push(`Trust score: ${result.trustScore?.toFixed(1) ?? "n/a"}/5 · ${result.reviewCount ?? 0} total reviews · showing ${reviews.length}`);
83
+ lines.push("");
84
+ if (reviews.length === 0) {
85
+ lines.push("No individual reviews available on the page.");
86
+ }
87
+ else {
88
+ reviews.forEach((r, i) => lines.push(renderReview(r, i + 1), ""));
89
+ }
90
+ const summary = {
91
+ found: true,
92
+ domain: result.domain,
93
+ name: result.name,
94
+ trustScore: result.trustScore,
95
+ reviewCount: result.reviewCount,
96
+ returned: reviews.length,
97
+ source: result.source,
98
+ reviews: reviews.map((r) => ({
99
+ author: r.author,
100
+ rating: r.rating,
101
+ date: r.date,
102
+ title: r.title,
103
+ body: r.body,
104
+ })),
105
+ };
106
+ lines.push("```json");
107
+ lines.push(JSON.stringify(summary, null, 2));
108
+ lines.push("```");
109
+ lines.push(`Page: ${result.source}`);
110
+ lines.push(SITE_LINK);
111
+ return lines.join("\n");
112
+ }
113
+ const server = new McpServer({
114
+ name: "sites-reviews-mcp",
115
+ version: "1.0.0",
116
+ });
117
+ server.registerTool("check_domain", {
118
+ title: "Check a domain's trust score",
119
+ description: "Check whether a website/company is trustworthy using Sites.Reviews. " +
120
+ "Returns the trust score (0–5), review count and a one-line verdict — " +
121
+ "useful before paying, signing up or sharing data with a site. " +
122
+ "Accepts a bare domain or a full URL (e.g. ozon.ru, https://bitmex.com).",
123
+ inputSchema: {
124
+ domain: z
125
+ .string()
126
+ .min(1)
127
+ .describe("Domain or URL to check, e.g. 'ozon.ru' or 'https://bitmex.com'."),
128
+ },
129
+ }, async ({ domain }) => {
130
+ try {
131
+ const result = await checkDomain(domain);
132
+ return { content: [{ type: "text", text: renderCheck(result) }] };
133
+ }
134
+ catch (err) {
135
+ const msg = err instanceof Error ? err.message : String(err);
136
+ return {
137
+ isError: true,
138
+ content: [
139
+ {
140
+ type: "text",
141
+ text: `Failed to check "${domain}" on Sites.Reviews: ${msg}`,
142
+ },
143
+ ],
144
+ };
145
+ }
146
+ });
147
+ server.registerTool("get_reviews", {
148
+ title: "Get recent reviews for a domain",
149
+ description: "Fetch recent user reviews for a website/company from Sites.Reviews: " +
150
+ "author, rating, date, title and body. Use this to understand WHY a " +
151
+ "site has its trust score. Accepts a bare domain or a full URL.",
152
+ inputSchema: {
153
+ domain: z
154
+ .string()
155
+ .min(1)
156
+ .describe("Domain or URL to fetch reviews for, e.g. '1ps.ru'."),
157
+ limit: z
158
+ .number()
159
+ .int()
160
+ .min(1)
161
+ .max(20)
162
+ .optional()
163
+ .describe("Maximum number of reviews to return (default 5, max 20)."),
164
+ },
165
+ }, async ({ domain, limit }) => {
166
+ const lim = limit ?? 5;
167
+ try {
168
+ const result = await checkDomain(domain);
169
+ return { content: [{ type: "text", text: renderReviews(result, lim) }] };
170
+ }
171
+ catch (err) {
172
+ const msg = err instanceof Error ? err.message : String(err);
173
+ return {
174
+ isError: true,
175
+ content: [
176
+ {
177
+ type: "text",
178
+ text: `Failed to get reviews for "${domain}" on Sites.Reviews: ${msg}`,
179
+ },
180
+ ],
181
+ };
182
+ }
183
+ });
184
+ async function main() {
185
+ const transport = new StdioServerTransport();
186
+ await server.connect(transport);
187
+ // stderr is safe for logs; stdout is reserved for the MCP protocol.
188
+ console.error("sites-reviews-mcp running on stdio");
189
+ }
190
+ main().catch((err) => {
191
+ console.error("Fatal error in sites-reviews-mcp:", err);
192
+ process.exit(1);
193
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Sites.Reviews data layer.
3
+ *
4
+ * Reads the public Sites.Reviews REST API (no auth, read-only):
5
+ * GET /api/public/v1/business/{domain} -> company summary + trust score
6
+ * GET /api/public/v1/reviews/{domain} -> recent published reviews
7
+ * The same data is also exposed as schema.org JSON-LD on each business page;
8
+ * this module uses the JSON API because it is stable and structured.
9
+ *
10
+ * Everything here is read-only, anonymous and polite. No auth, no secrets.
11
+ */
12
+ export declare const SITE_BASE = "https://sites.reviews";
13
+ export declare const API_BASE = "https://sites.reviews/api/public/v1";
14
+ export declare const USER_AGENT = "sites-reviews-mcp/1.0 (+https://github.com/SitesReviewsTrust/sites-reviews-mcp)";
15
+ export interface Review {
16
+ author: string;
17
+ rating: number | null;
18
+ date: string | null;
19
+ title: string | null;
20
+ body: string;
21
+ positives: string[];
22
+ }
23
+ export interface BusinessResult {
24
+ found: boolean;
25
+ /** The normalised domain that actually resolved (may differ from input). */
26
+ domain: string;
27
+ name?: string;
28
+ url?: string;
29
+ /** Trust rating on a 0–5 scale (avg_ratings). */
30
+ trustScore?: number;
31
+ reviewCount?: number;
32
+ verdict?: string;
33
+ reviews?: Review[];
34
+ /** The Sites.Reviews page this data refers to. */
35
+ source?: string;
36
+ /** Human-readable note, mainly used when found === false. */
37
+ message?: string;
38
+ }
39
+ /**
40
+ * Normalise arbitrary user input into a Sites.Reviews domain slug.
41
+ * Strips protocol, path, query, port, auth and whitespace; lowercases.
42
+ * "https://www.Ozon.ru/foo?x=1" -> "www.ozon.ru"
43
+ */
44
+ export declare function normalizeDomain(input: string): string;
45
+ export declare function pageUrl(domain: string): string;
46
+ type FetchLike = typeof fetch;
47
+ /**
48
+ * Look up a business on Sites.Reviews via the public API.
49
+ *
50
+ * Tries the normalised domain, and on 404 retries the www-toggled variant.
51
+ * Returns { found: false, ... } when the company is not in the catalog.
52
+ *
53
+ * @param input Raw domain or URL.
54
+ * @param opts.bodyLimit Max characters per review body (default 400).
55
+ * @param opts.maxReviews Max reviews to fetch (default 20).
56
+ * @param opts.fetchImpl Override for testing (defaults to global fetch).
57
+ */
58
+ export declare function checkDomain(input: string, opts?: {
59
+ bodyLimit?: number;
60
+ maxReviews?: number;
61
+ fetchImpl?: FetchLike;
62
+ }): Promise<BusinessResult>;
63
+ export {};
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Sites.Reviews data layer.
3
+ *
4
+ * Reads the public Sites.Reviews REST API (no auth, read-only):
5
+ * GET /api/public/v1/business/{domain} -> company summary + trust score
6
+ * GET /api/public/v1/reviews/{domain} -> recent published reviews
7
+ * The same data is also exposed as schema.org JSON-LD on each business page;
8
+ * this module uses the JSON API because it is stable and structured.
9
+ *
10
+ * Everything here is read-only, anonymous and polite. No auth, no secrets.
11
+ */
12
+ export const SITE_BASE = "https://sites.reviews";
13
+ export const API_BASE = `${SITE_BASE}/api/public/v1`;
14
+ export const USER_AGENT = "sites-reviews-mcp/1.0 (+https://github.com/SitesReviewsTrust/sites-reviews-mcp)";
15
+ // A real browser UA is sent as well — the site sits behind Cloudflare, which
16
+ // can challenge generic clients. We identify ourselves honestly in X-Client.
17
+ const BROWSER_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
18
+ "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
19
+ /**
20
+ * Normalise arbitrary user input into a Sites.Reviews domain slug.
21
+ * Strips protocol, path, query, port, auth and whitespace; lowercases.
22
+ * "https://www.Ozon.ru/foo?x=1" -> "www.ozon.ru"
23
+ */
24
+ export function normalizeDomain(input) {
25
+ let d = (input || "").trim().toLowerCase();
26
+ d = d.replace(/^[a-z]+:\/\//, ""); // protocol
27
+ d = d.replace(/^[^@/]*@/, ""); // user:pass@
28
+ d = d.split("/")[0]; // path
29
+ d = d.split("?")[0]; // query
30
+ d = d.split("#")[0]; // fragment
31
+ d = d.split(":")[0]; // port
32
+ return d.trim();
33
+ }
34
+ /** Candidate domains to try, in order: as-is, then toggle leading `www.`. */
35
+ function domainCandidates(domain) {
36
+ const out = [domain];
37
+ if (domain.startsWith("www."))
38
+ out.push(domain.slice(4));
39
+ else
40
+ out.push("www." + domain);
41
+ return [...new Set(out)];
42
+ }
43
+ export function pageUrl(domain) {
44
+ return `${SITE_BASE}/businesses/${encodeURIComponent(domain)}`;
45
+ }
46
+ const HEADERS = {
47
+ "User-Agent": BROWSER_UA,
48
+ "X-Client": USER_AGENT,
49
+ Accept: "application/json",
50
+ "Accept-Language": "en,ru;q=0.8",
51
+ };
52
+ /** GET a public API path. Returns parsed JSON, or null on 404. Throws on other errors. */
53
+ async function apiGet(path, fetchImpl) {
54
+ const res = await fetchImpl(`${API_BASE}${path}`, {
55
+ headers: HEADERS,
56
+ redirect: "follow",
57
+ });
58
+ if (res.status === 404)
59
+ return null;
60
+ if (!res.ok) {
61
+ throw new Error(`Sites.Reviews API returned HTTP ${res.status} for ${path}`);
62
+ }
63
+ return await res.json();
64
+ }
65
+ function toNumber(v) {
66
+ if (v === null || v === undefined)
67
+ return null;
68
+ const n = typeof v === "number" ? v : parseFloat(String(v));
69
+ return Number.isFinite(n) ? n : null;
70
+ }
71
+ function buildVerdict(score, count) {
72
+ if (score === null)
73
+ return "No trust score available yet.";
74
+ const c = count ?? 0;
75
+ let band;
76
+ if (score >= 4.5)
77
+ band = "Excellent reputation";
78
+ else if (score >= 4.0)
79
+ band = "Good reputation";
80
+ else if (score >= 3.0)
81
+ band = "Mixed reputation — read reviews carefully";
82
+ else if (score >= 2.0)
83
+ band = "Poor reputation — proceed with caution";
84
+ else
85
+ band = "Very poor reputation — high risk";
86
+ return `${band} (${score.toFixed(1)}/5 from ${c} review${c === 1 ? "" : "s"}).`;
87
+ }
88
+ function truncate(s, max) {
89
+ if (s.length <= max)
90
+ return s;
91
+ return s.slice(0, max - 1).trimEnd() + "…";
92
+ }
93
+ function asStringArray(v) {
94
+ if (!Array.isArray(v))
95
+ return [];
96
+ return v
97
+ .map((x) => (typeof x === "string" ? x : x?.title ?? x?.text))
98
+ .filter((x) => typeof x === "string" && x.length > 0);
99
+ }
100
+ function parseReviews(rows, bodyLimit) {
101
+ return (Array.isArray(rows) ? rows : []).map((r) => ({
102
+ author: r?.author ? String(r.author) : "Anonymous",
103
+ rating: toNumber(r?.stars),
104
+ date: r?.created_at ? String(r.created_at) : null,
105
+ title: r?.title ? String(r.title) : null,
106
+ body: r?.body ? truncate(String(r.body), bodyLimit) : "",
107
+ positives: asStringArray(r?.pros),
108
+ }));
109
+ }
110
+ /**
111
+ * Look up a business on Sites.Reviews via the public API.
112
+ *
113
+ * Tries the normalised domain, and on 404 retries the www-toggled variant.
114
+ * Returns { found: false, ... } when the company is not in the catalog.
115
+ *
116
+ * @param input Raw domain or URL.
117
+ * @param opts.bodyLimit Max characters per review body (default 400).
118
+ * @param opts.maxReviews Max reviews to fetch (default 20).
119
+ * @param opts.fetchImpl Override for testing (defaults to global fetch).
120
+ */
121
+ export async function checkDomain(input, opts = {}) {
122
+ const fetchImpl = opts.fetchImpl ?? fetch;
123
+ const bodyLimit = opts.bodyLimit ?? 400;
124
+ const maxReviews = opts.maxReviews ?? 20;
125
+ const normalized = normalizeDomain(input);
126
+ if (!normalized || !/[a-z0-9.-]/i.test(normalized)) {
127
+ return {
128
+ found: false,
129
+ domain: normalized,
130
+ message: `"${input}" is not a valid domain.`,
131
+ };
132
+ }
133
+ let biz = null;
134
+ let resolvedDomain = normalized;
135
+ for (const candidate of domainCandidates(normalized)) {
136
+ biz = await apiGet(`/business/${encodeURIComponent(candidate)}`, fetchImpl);
137
+ if (biz && !biz.error) {
138
+ resolvedDomain = candidate;
139
+ break;
140
+ }
141
+ biz = null;
142
+ }
143
+ if (!biz) {
144
+ return {
145
+ found: false,
146
+ domain: normalized,
147
+ source: pageUrl(normalized),
148
+ message: `"${normalized}" is not yet in the Sites.Reviews catalog. ` +
149
+ `Anyone can be the first to review it at ${pageUrl(normalized)}.`,
150
+ };
151
+ }
152
+ const trustScore = toNumber(biz.avg_ratings);
153
+ const reviewCount = toNumber(biz.total_reviews);
154
+ const source = biz.url ? String(biz.url) : pageUrl(resolvedDomain);
155
+ // Pull recent reviews (best-effort — a summary without reviews is still useful).
156
+ let reviews = [];
157
+ try {
158
+ const rv = await apiGet(`/reviews/${encodeURIComponent(resolvedDomain)}?per_page=${maxReviews}`, fetchImpl);
159
+ if (rv && Array.isArray(rv.reviews))
160
+ reviews = parseReviews(rv.reviews, bodyLimit);
161
+ }
162
+ catch {
163
+ // Non-fatal: keep the summary even if the reviews call fails.
164
+ }
165
+ return {
166
+ found: true,
167
+ domain: resolvedDomain,
168
+ name: biz.name ? String(biz.name) : resolvedDomain,
169
+ url: source,
170
+ trustScore: trustScore ?? undefined,
171
+ reviewCount: reviewCount ?? undefined,
172
+ verdict: buildVerdict(trustScore, reviewCount),
173
+ reviews,
174
+ source,
175
+ };
176
+ }
@@ -0,0 +1,107 @@
1
+ # Usage walkthroughs
2
+
3
+ Real-world prompt → tool call → assistant answer flows for the **Sites.Reviews MCP server**.
4
+ Once the server is connected (see the [README](../README.md#-install-anywhere)),
5
+ just ask your assistant in plain language — it picks the right tool on its own.
6
+
7
+ The JSON blocks below are illustrative of the structured summary each tool returns;
8
+ exact scores and counts change over time as new reviews are published.
9
+
10
+ ---
11
+
12
+ ## 1. "Is this shop safe to buy from?"
13
+
14
+ > **You:** Is **ozon.ru** safe to buy from?
15
+
16
+ The assistant calls `check_domain`:
17
+
18
+ ```json
19
+ { "domain": "ozon.ru" }
20
+ ```
21
+
22
+ Tool returns:
23
+
24
+ ```json
25
+ {
26
+ "found": true,
27
+ "domain": "ozon.ru",
28
+ "name": "Ozon",
29
+ "trustScore": 4.2,
30
+ "reviewCount": 128,
31
+ "verdict": "Good reputation (4.2/5 from 128 reviews).",
32
+ "source": "https://sites.reviews/businesses/ozon.ru"
33
+ }
34
+ ```
35
+
36
+ > **Assistant:** Yes — **ozon.ru** has a **good reputation** on Sites.Reviews: a
37
+ > trust score of **4.2/5** across **128 reviews**. Buyers generally report
38
+ > reliable delivery and working refunds. I'd still pay with a method that offers
39
+ > buyer protection. Source: https://sites.reviews/businesses/ozon.ru
40
+
41
+ ---
42
+
43
+ ## 2. "Check this site before I sign up"
44
+
45
+ > **You:** Check **bitmex.com** before I create an account.
46
+
47
+ The assistant calls `check_domain` with `{ "domain": "bitmex.com" }`, gets the
48
+ trust score and verdict, then summarises the risk band and points you to the page
49
+ to read the full reviews. If the verdict is "Mixed" or "Poor", it will suggest
50
+ calling `get_reviews` to see *why*.
51
+
52
+ ---
53
+
54
+ ## 3. "Why does this site have a low score?"
55
+
56
+ > **You:** Show me recent reviews for **1ps.ru** and tell me what people complain about.
57
+
58
+ The assistant calls `get_reviews`:
59
+
60
+ ```json
61
+ { "domain": "1ps.ru", "limit": 5 }
62
+ ```
63
+
64
+ Tool returns the trust score plus the most recent reviews (author, rating, date,
65
+ title, body). The assistant then synthesises the themes:
66
+
67
+ > **Assistant:** **1ps.ru** scores **4.8/5** from 34 reviews. Recent feedback is
68
+ > mostly positive — users praise the SEO promotion results and responsive
69
+ > support. The handful of lower ratings mention pricing. On balance, a strong
70
+ > reputation. Source: https://sites.reviews/businesses/1ps.ru
71
+
72
+ ---
73
+
74
+ ## 4. "Is this a scam?" (full URL, unknown domain)
75
+
76
+ > **You:** Is this a scam? `https://some-shop.example/checkout?ref=ad`
77
+
78
+ The assistant normalises the URL to `some-shop.example` and calls `check_domain`.
79
+ When the domain isn't in the catalog yet:
80
+
81
+ ```json
82
+ {
83
+ "found": false,
84
+ "domain": "some-shop.example",
85
+ "message": "\"some-shop.example\" is not yet in the Sites.Reviews catalog. Anyone can be the first to review it at https://sites.reviews/businesses/some-shop.example."
86
+ }
87
+ ```
88
+
89
+ > **Assistant:** I couldn't find **some-shop.example** in the Sites.Reviews
90
+ > catalog, so there's no independent reputation data yet. That's not proof it's a
91
+ > scam, but treat an unknown shop with caution: verify contact details, look for
92
+ > HTTPS, and prefer a payment method with chargeback protection. You can be the
93
+ > first to review it at https://sites.reviews/businesses/some-shop.example
94
+
95
+ ---
96
+
97
+ ## 5. "Compare two providers"
98
+
99
+ > **You:** I'm choosing between **aliexpress.com** and another marketplace —
100
+ > which is more trusted?
101
+
102
+ The assistant calls `check_domain` **twice** (once per domain) and compares the
103
+ two trust scores and review counts side by side, then recommends the better-rated
104
+ option while flagging any notable complaint themes from `get_reviews`.
105
+
106
+ This "call the tool once per entity, then compare" pattern works for any number
107
+ of domains you want to evaluate against each other.
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "sites-reviews": {
4
+ "command": "npx",
5
+ "args": ["-y", "@sitesreviews/mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "sites-reviews": {
4
+ "command": "npx",
5
+ "args": ["-y", "@sitesreviews/mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "servers": {
3
+ "sites-reviews": {
4
+ "type": "stdio",
5
+ "command": "npx",
6
+ "args": ["-y", "@sitesreviews/mcp"]
7
+ }
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@sitesreviews/mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Sites.Reviews — give Claude, Cursor & any AI assistant a website's trust score, real user reviews and scam-check signals before you trust or pay it. Domain reputation & due-diligence for AI agents.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "sites-reviews-mcp": "dist/index.js"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "examples",
16
+ "README.md",
17
+ "CHANGELOG.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "start": "node dist/index.js",
23
+ "test": "node --import tsx test/sites-reviews.test.ts",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "mcp-server",
30
+ "mcp-tools",
31
+ "ai-tools",
32
+ "llm-tools",
33
+ "ai-agent",
34
+ "agent-tools",
35
+ "anthropic",
36
+ "claude",
37
+ "claude-desktop",
38
+ "claude-code",
39
+ "cursor",
40
+ "windsurf",
41
+ "cline",
42
+ "chatgpt",
43
+ "openai",
44
+ "sites-reviews",
45
+ "trust-score",
46
+ "trust-rating",
47
+ "domain-trust",
48
+ "domain-reputation",
49
+ "website-reviews",
50
+ "company-reviews",
51
+ "business-reviews",
52
+ "consumer-reviews",
53
+ "reviews",
54
+ "ratings",
55
+ "reputation",
56
+ "scam-check",
57
+ "scam-detector",
58
+ "fraud-detection",
59
+ "phishing-check",
60
+ "website-checker",
61
+ "website-safety",
62
+ "is-it-safe",
63
+ "due-diligence",
64
+ "trustpilot-alternative"
65
+ ],
66
+ "author": "Sites.Reviews",
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+https://github.com/SitesReviewsTrust/sites-reviews-mcp.git"
70
+ },
71
+ "homepage": "https://sites.reviews",
72
+ "bugs": {
73
+ "url": "https://github.com/SitesReviewsTrust/sites-reviews-mcp/issues"
74
+ },
75
+ "engines": {
76
+ "node": ">=18"
77
+ },
78
+ "dependencies": {
79
+ "@modelcontextprotocol/sdk": "^1.29.0",
80
+ "zod": "^3.25.0"
81
+ },
82
+ "devDependencies": {
83
+ "@types/node": "^22.10.0",
84
+ "tsx": "^4.19.2",
85
+ "typescript": "^5.7.2"
86
+ }
87
+ }