@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 +40 -0
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +193 -0
- package/dist/sites-reviews.d.ts +63 -0
- package/dist/sites-reviews.js +176 -0
- package/examples/USAGE.md +107 -0
- package/examples/claude_desktop_config.json +8 -0
- package/examples/cursor_config.json +8 -0
- package/examples/vscode_mcp.json +9 -0
- package/package.json +87 -0
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
|
+
[](https://www.npmjs.com/package/@sitesreviews/mcp)
|
|
8
|
+
[](https://github.com/SitesReviewsTrust/sites-reviews-mcp/actions/workflows/ci.yml)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](https://modelcontextprotocol.io)
|
|
11
|
+
[](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
|
package/dist/index.d.ts
ADDED
|
@@ -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.
|
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
|
+
}
|