@recomention/mcp 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +152 -2
- package/dist/api.js +104 -0
- package/dist/index.js +30 -0
- package/dist/server.js +113 -0
- package/dist/suggest.js +47 -0
- package/package.json +60 -3
- package/skills/ai-visibility-check/SKILL.md +44 -0
- package/skills/competitor-watch/SKILL.md +33 -0
- package/skills/distribution-kit/SKILL.md +32 -0
- package/skills/fix-list-review/SKILL.md +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Astrael OÜ
|
|
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
CHANGED
|
@@ -1,3 +1,153 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @recomention/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An MCP server for [recomention](https://www.recomention.com): ask Claude Code, Cursor or Codex
|
|
4
|
+
*"is my product recommended by ChatGPT for meeting scheduling?"* and have it start a report, wait
|
|
5
|
+
for it, and read the result — every engine's "named in n of m answers", who is recommended instead,
|
|
6
|
+
the sources the engines read, and the fix list.
|
|
7
|
+
|
|
8
|
+
It is a thin stdio client for the public JSON API v1 at `https://api.recomention.com/v1`. It holds no state.
|
|
9
|
+
It runs locally, as a process your MCP client starts. For ChatGPT, Claude.ai and other clients that
|
|
10
|
+
connect to a remote server, use the hosted connector at `https://mcp.recomention.com/mcp` instead: it
|
|
11
|
+
signs in with your recomention account (OAuth), no API key needed. Setup for each client:
|
|
12
|
+
<https://docs.recomention.com/mcp/>.
|
|
13
|
+
|
|
14
|
+
Requires Node.js 20 or newer (`npx` comes with it).
|
|
15
|
+
|
|
16
|
+
## Environment
|
|
17
|
+
|
|
18
|
+
| Variable | | |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `RECOMENTION_API_KEY` | required for reports | Create one at `/account` → Developers. Shown once. Without it, public rankings and suggested questions still work. |
|
|
21
|
+
| `RECOMENTION_BASE_URL` | optional | Defaults to `https://api.recomention.com`. An older `https://www.recomention.com` (or the apex, a web preview, or a local Next dev server on port 3000–3099) still works: it reaches the site's deprecated `/api/v1` alias. Any other bare origin, such as the API preview Worker or a local API Worker (`http://localhost:8790`), uses `/v1`. |
|
|
22
|
+
| `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `PERPLEXITY_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `DATAFORSEO_API_KEY` (Google AI Overview, `login:password`) | optional | Only for `funding: "byok"` (Bring Your Own Key), which needs an active Agent plan on the account. Any subset works and engines without a key are reported as skipped, but one of OpenAI, Anthropic or Gemini is required. Read from this server's environment, never from a tool argument, so your keys never pass through the model's context. |
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
### Claude Code
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
claude mcp add recomention \
|
|
30
|
+
--env RECOMENTION_API_KEY=YOUR_API_KEY \
|
|
31
|
+
--env RECOMENTION_BASE_URL=https://api.recomention.com \
|
|
32
|
+
-- npx -y @recomention/mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Replace `YOUR_API_KEY` with your key. Add `--scope user` to use it in every project. Check it with `claude mcp list`, or `/mcp` inside a session.
|
|
36
|
+
|
|
37
|
+
### Cursor (`~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project) and other JSON-configured clients
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"recomention": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "@recomention/mcp"],
|
|
45
|
+
"env": { "RECOMENTION_API_KEY": "<key>", "RECOMENTION_BASE_URL": "https://api.recomention.com" }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Keep a project-level `.cursor/mcp.json` that holds a key out of version control.
|
|
52
|
+
|
|
53
|
+
### Codex
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
codex mcp add recomention \
|
|
57
|
+
--env RECOMENTION_API_KEY=YOUR_API_KEY \
|
|
58
|
+
--env RECOMENTION_BASE_URL=https://api.recomention.com \
|
|
59
|
+
-- npx -y @recomention/mcp
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
or in `~/.codex/config.toml`:
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
[mcp_servers.recomention]
|
|
66
|
+
command = "npx"
|
|
67
|
+
args = ["-y", "@recomention/mcp"]
|
|
68
|
+
env = { RECOMENTION_API_KEY = "<key>", RECOMENTION_BASE_URL = "https://api.recomention.com" }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Tools
|
|
72
|
+
|
|
73
|
+
| Tool | Cost | |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `recomention_rankings_list` | free | Public AI rankings. No key needed. |
|
|
76
|
+
| `recomention_rankings_get(slug)` | free | One ranking with every answer it counted. |
|
|
77
|
+
| `recomention_suggest_prompts({domain, category, description})` | free | Buyer questions from fixed templates. No model call. Never names the product. |
|
|
78
|
+
| `recomention_report_start({domain, description, category, plan, prompts?, engines?, funding})` | **spends** | `funding: "credits"` uses one agency credit (plan `FULL`); `funding: "byok"` runs on your provider keys and needs an active Agent plan; a refusal names the plan limit it hit. |
|
|
79
|
+
| `recomention_report_status(slug)` | free | PENDING / RUNNING / COMPLETE / FAILED, with progress. |
|
|
80
|
+
| `recomention_report_get(slug)` | free | The full report JSON. |
|
|
81
|
+
| `recomention_distribution_kit(slug)` | free | Markdown files built from a finished report: product, competitors, AI visibility, where to get mentioned, channel plan. |
|
|
82
|
+
| `recomention_reports_list` | free | Your reports, newest first. |
|
|
83
|
+
|
|
84
|
+
Reading results honestly: an engine in `enginesSkipped` did not run (for example *"provider rejected
|
|
85
|
+
the key"*) — it is never an engine that left the product out. A report with `simulated: true` is test
|
|
86
|
+
data.
|
|
87
|
+
|
|
88
|
+
## Setup prompt
|
|
89
|
+
|
|
90
|
+
The account page's **Copy setup prompt** button copies this (with the site's own URL). Paste it into
|
|
91
|
+
your agent:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
Set up the recomention MCP server so you can check whether AI engines (ChatGPT, Claude, Perplexity, Gemini, Grok) recommend a product.
|
|
95
|
+
|
|
96
|
+
1. Add this MCP server to your MCP configuration (Claude Code: `claude mcp add`; Cursor: ~/.cursor/mcp.json; Codex: ~/.codex/config.toml):
|
|
97
|
+
command: npx -y @recomention/mcp
|
|
98
|
+
env: RECOMENTION_API_KEY=<ask me for my API key; never write it into a repository>
|
|
99
|
+
RECOMENTION_BASE_URL=https://api.recomention.com
|
|
100
|
+
Claude Code one-liner: claude mcp add recomention --env RECOMENTION_API_KEY=<key> --env RECOMENTION_BASE_URL=https://api.recomention.com -- npx -y @recomention/mcp
|
|
101
|
+
2. Restart or reload MCP servers, then call recomention_rankings_list to confirm it works.
|
|
102
|
+
|
|
103
|
+
How to use it:
|
|
104
|
+
- To check a product, first call recomention_suggest_prompts with its domain, category and a one-line description, show me the questions, and let me edit them.
|
|
105
|
+
- Starting a report spends money: an agency credit, or calls on my own provider keys (that needs my Agent plan). Always ask me before calling recomention_report_start, and say which funding it will use.
|
|
106
|
+
- On my own keys, any subset works, but one of OpenAI, Anthropic or Gemini is required. If the start is refused with a `limit` (plan, projects, reportsPerMonth, perDay, concurrent), tell me which limit and do not retry around it.
|
|
107
|
+
- Reports take several minutes. Poll recomention_report_status about once a minute; do not start a second report for the same product while one is running.
|
|
108
|
+
- When it is COMPLETE, call recomention_report_get and summarise: the overall score, each engine's "named in n of m answers", who is recommended instead, and the top three fixes. Quote counts as "n of m", never as yes/no.
|
|
109
|
+
- An engine listed under enginesSkipped did not run (for example "provider rejected the key"). Say so; never describe it as an engine that did not mention the product.
|
|
110
|
+
- A report with simulated: true is a test run with made-up answers. Say that plainly and do not draw conclusions from it.
|
|
111
|
+
|
|
112
|
+
Docs: https://www.recomention.com/developers
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The canonical text lives in `packages/core/src/config/developers.ts` (`setupPrompt`); keep this copy
|
|
116
|
+
in step with it.
|
|
117
|
+
|
|
118
|
+
## Skills
|
|
119
|
+
|
|
120
|
+
`skills/` holds agent skills in the slash-command style: copy a folder into `.claude/skills/` (Claude
|
|
121
|
+
Code) or your agent's equivalent.
|
|
122
|
+
|
|
123
|
+
- `ai-visibility-check` — check one product end to end.
|
|
124
|
+
- `fix-list-review` — turn a finished report into a prioritised, honest plan of work.
|
|
125
|
+
- `competitor-watch` — compare who the engines name in a category, from public rankings and your reports.
|
|
126
|
+
- `distribution-kit` — read a finished report's kit and work through its channel plan, one channel at a time.
|
|
127
|
+
|
|
128
|
+
## Develop
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm install
|
|
132
|
+
npm test # in-memory client against the server, fake API; metadata/version checks
|
|
133
|
+
npm run smoke # spawns the stdio server against RECOMENTION_BASE_URL
|
|
134
|
+
npm run build # clean dist/ (bin: recomention-mcp)
|
|
135
|
+
npm run smoke:pack # packs the tarball (with pnpm), checks its contents, runs it against a fake local API
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
In the monorepo, run them as `pnpm --filter @recomention/mcp <script>`.
|
|
139
|
+
|
|
140
|
+
Run `smoke --start` only against a local server with `RECOMENTION_MOCK=1`: against a live
|
|
141
|
+
deployment it spends a credit.
|
|
142
|
+
|
|
143
|
+
`prepack` empties and rebuilds `dist/`, so a pack or publish always ships the current sources and
|
|
144
|
+
no test files. `version` lives in `package.json`; the server reads it at startup, and `server.json`
|
|
145
|
+
(the MCP Registry entry, `io.github.recomention/mcp`) must carry the same number — a test fails
|
|
146
|
+
otherwise.
|
|
147
|
+
|
|
148
|
+
This repository is published from the recomention monorepo, where development happens. Bugs and
|
|
149
|
+
questions: [issues](https://github.com/recomention/mcp/issues) or support@recomention.com.
|
|
150
|
+
|
|
151
|
+
## Licence
|
|
152
|
+
|
|
153
|
+
[MIT](./LICENSE).
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A thin client for the recomention JSON API v1. Everything the MCP server
|
|
3
|
+
* does goes through here; it holds no state and no business rules of its own.
|
|
4
|
+
*/
|
|
5
|
+
export const PROVIDERS = ["OPENAI", "ANTHROPIC", "PERPLEXITY", "GEMINI", "GROK", "AI_OVERVIEW"];
|
|
6
|
+
/** Where BYOK keys come from: the MCP server's own environment, never a tool argument. */
|
|
7
|
+
export const PROVIDER_ENV = {
|
|
8
|
+
OPENAI: "OPENAI_API_KEY",
|
|
9
|
+
ANTHROPIC: "ANTHROPIC_API_KEY",
|
|
10
|
+
PERPLEXITY: "PERPLEXITY_API_KEY",
|
|
11
|
+
GEMINI: "GEMINI_API_KEY",
|
|
12
|
+
GROK: "XAI_API_KEY",
|
|
13
|
+
// Google AI Overview through DataForSEO: the API login and password as `login:password`.
|
|
14
|
+
AI_OVERVIEW: "DATAFORSEO_API_KEY",
|
|
15
|
+
};
|
|
16
|
+
export function providerKeysFromEnv(env = process.env) {
|
|
17
|
+
const keys = {};
|
|
18
|
+
for (const p of PROVIDERS) {
|
|
19
|
+
const value = env[PROVIDER_ENV[p]]?.trim();
|
|
20
|
+
if (value)
|
|
21
|
+
keys[p] = value;
|
|
22
|
+
}
|
|
23
|
+
return keys;
|
|
24
|
+
}
|
|
25
|
+
export class ApiError extends Error {
|
|
26
|
+
status;
|
|
27
|
+
constructor(status, message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.status = status;
|
|
30
|
+
this.name = "ApiError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Where the hosted API lives. Paths are /v1/... */
|
|
34
|
+
export const DEFAULT_BASE_URL = "https://api.recomention.com";
|
|
35
|
+
/**
|
|
36
|
+
* The v1 root under a configured base URL. A copy of packages/core/src/shared/api-base.ts
|
|
37
|
+
* (this package is published and imports no workspace code): keep the two in step.
|
|
38
|
+
*
|
|
39
|
+
* .../v1, .../api, .../api/v1 -> used as written (.../api gains /v1)
|
|
40
|
+
* a bare site origin -> <origin>/api/v1 (the site's deprecated alias: an older config still works)
|
|
41
|
+
* www.recomention.com, recomention.com, a *recomention-web-preview.*.workers.dev
|
|
42
|
+
* host, or localhost/127.0.0.1 on ports 3000-3099 (a local Next dev server)
|
|
43
|
+
* any other bare origin -> <origin>/v1 (the API host, its preview Worker, a local API Worker)
|
|
44
|
+
*/
|
|
45
|
+
export function apiV1Root(base) {
|
|
46
|
+
const url = new URL(base);
|
|
47
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
48
|
+
const origin = url.origin;
|
|
49
|
+
if (path.endsWith("/v1"))
|
|
50
|
+
return `${origin}${path}`;
|
|
51
|
+
if (path.endsWith("/api"))
|
|
52
|
+
return `${origin}${path}/v1`;
|
|
53
|
+
if (path)
|
|
54
|
+
return `${origin}${path}/v1`;
|
|
55
|
+
return isSiteOrigin(url) ? `${origin}/api/v1` : `${origin}/v1`;
|
|
56
|
+
}
|
|
57
|
+
function isSiteOrigin(url) {
|
|
58
|
+
const host = url.hostname.toLowerCase();
|
|
59
|
+
if (host === "recomention.com" || host === "www.recomention.com")
|
|
60
|
+
return true;
|
|
61
|
+
if (/(^|-)recomention-web-preview\.[a-z0-9-]+\.workers\.dev$/.test(host))
|
|
62
|
+
return true;
|
|
63
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "[::1]") {
|
|
64
|
+
const port = Number(url.port || (url.protocol === "https:" ? 443 : 80));
|
|
65
|
+
return port >= 3000 && port <= 3099;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
export function createApi(options) {
|
|
70
|
+
const base = options.baseUrl.replace(/\/+$/, "");
|
|
71
|
+
const root = apiV1Root(base);
|
|
72
|
+
const doFetch = options.fetch ?? fetch;
|
|
73
|
+
async function call(method, path, body, auth = true) {
|
|
74
|
+
if (auth && !options.apiKey)
|
|
75
|
+
throw new ApiError(401, "RECOMENTION_API_KEY is not set. Create a key in your recomention account (Developers) and add it to this MCP server's env.");
|
|
76
|
+
const headers = { accept: "application/json", "user-agent": options.userAgent ?? "recomention-mcp" };
|
|
77
|
+
if (auth && options.apiKey)
|
|
78
|
+
headers.authorization = `Bearer ${options.apiKey}`;
|
|
79
|
+
if (body !== undefined)
|
|
80
|
+
headers["content-type"] = "application/json";
|
|
81
|
+
let res;
|
|
82
|
+
try {
|
|
83
|
+
res = await doFetch(`${root}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(30_000) });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw new ApiError(0, `Couldn't reach ${base}: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
const data = (await res.json().catch(() => null));
|
|
89
|
+
if (!res.ok)
|
|
90
|
+
throw new ApiError(res.status, data?.error ?? `HTTP ${res.status}`);
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
baseUrl: base,
|
|
95
|
+
root,
|
|
96
|
+
rankings: () => call("GET", "/rankings", undefined, false),
|
|
97
|
+
ranking: (slug) => call("GET", `/rankings/${encodeURIComponent(slug)}`, undefined, false),
|
|
98
|
+
reports: () => call("GET", "/reports"),
|
|
99
|
+
report: (slug) => call("GET", `/reports/${encodeURIComponent(slug)}`),
|
|
100
|
+
kit: (slug) => call("GET", `/reports/${encodeURIComponent(slug)}/kit`),
|
|
101
|
+
status: (slug) => call("GET", `/reports/${encodeURIComponent(slug)}/status`),
|
|
102
|
+
start: (body) => call("POST", "/reports", body),
|
|
103
|
+
};
|
|
104
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* recomention MCP server over stdio.
|
|
4
|
+
*
|
|
5
|
+
* RECOMENTION_API_KEY an API key from your account (Developers). Needed for reports.
|
|
6
|
+
* RECOMENTION_BASE_URL defaults to https://api.recomention.com (an older https://www.recomention.com still works)
|
|
7
|
+
*
|
|
8
|
+
* stdout carries the protocol, so anything human goes to stderr.
|
|
9
|
+
*/
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { createApi, DEFAULT_BASE_URL } from "./api.js";
|
|
12
|
+
import { createServer, SERVER_VERSION } from "./server.js";
|
|
13
|
+
const baseUrl = process.env.RECOMENTION_BASE_URL?.trim() || DEFAULT_BASE_URL;
|
|
14
|
+
// The API key travels as a bearer token: refuse plain http anywhere but this machine.
|
|
15
|
+
{
|
|
16
|
+
let parsed = null;
|
|
17
|
+
try {
|
|
18
|
+
parsed = new URL(baseUrl);
|
|
19
|
+
}
|
|
20
|
+
catch { /* reported below */ }
|
|
21
|
+
const local = parsed !== null && ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
|
|
22
|
+
if (!parsed || (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:"))) {
|
|
23
|
+
console.error(`RECOMENTION_BASE_URL must be an https:// URL (http:// only for localhost). Got: ${baseUrl}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const api = createApi({ baseUrl, apiKey: process.env.RECOMENTION_API_KEY?.trim() || undefined, userAgent: `recomention-mcp/${SERVER_VERSION}` });
|
|
28
|
+
const server = createServer(api);
|
|
29
|
+
await server.connect(new StdioServerTransport());
|
|
30
|
+
console.error(`recomention MCP server ${SERVER_VERSION} on stdio → ${api.root}${process.env.RECOMENTION_API_KEY ? "" : " (no RECOMENTION_API_KEY: rankings only)"}`);
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { ApiError, PROVIDERS, providerKeysFromEnv } from "./api.js";
|
|
5
|
+
import { suggestPrompts } from "./suggest.js";
|
|
6
|
+
export const SERVER_NAME = "recomention";
|
|
7
|
+
/**
|
|
8
|
+
* package.json's version, read at startup: one number for npm, the MCP handshake and the
|
|
9
|
+
* user agent. `../package.json` is the package root from both src/ and dist/, and npm always
|
|
10
|
+
* ships it. server.json carries the same number for the registry; a test keeps them in step.
|
|
11
|
+
*/
|
|
12
|
+
export const SERVER_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
13
|
+
function ok(value) {
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
15
|
+
}
|
|
16
|
+
function fail(error) {
|
|
17
|
+
const message = error instanceof ApiError ? `${error.status ? `HTTP ${error.status}: ` : ""}${error.message}` : error instanceof Error ? error.message : String(error);
|
|
18
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
19
|
+
}
|
|
20
|
+
async function run(fn) {
|
|
21
|
+
try {
|
|
22
|
+
return ok(await fn());
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return fail(error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const slug = z.string().min(1).max(100).describe("The slug from a previous call.");
|
|
29
|
+
/**
|
|
30
|
+
* The recomention tools. Read tools are free. `recomention_report_start` is the
|
|
31
|
+
* only tool that spends anything (an agency credit, or calls on the user's own
|
|
32
|
+
* provider keys), and its description says so, so an agent asks first.
|
|
33
|
+
*/
|
|
34
|
+
export function createServer(api, env = process.env) {
|
|
35
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
36
|
+
server.registerTool("recomention_rankings_list", {
|
|
37
|
+
title: "List AI rankings",
|
|
38
|
+
description: "List recomention's public AI rankings: for each category, which products the AI engines name when asked its buyer questions, month by month. Free.",
|
|
39
|
+
inputSchema: {},
|
|
40
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
41
|
+
}, () => run(() => api.rankings()));
|
|
42
|
+
server.registerTool("recomention_rankings_get", {
|
|
43
|
+
title: "Get an AI ranking",
|
|
44
|
+
description: "One public AI ranking by slug: every product named, how many answers named it (as n of m), and the verbatim answers. Free.",
|
|
45
|
+
inputSchema: { slug },
|
|
46
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
47
|
+
}, ({ slug }) => run(() => api.ranking(slug)));
|
|
48
|
+
server.registerTool("recomention_suggest_prompts", {
|
|
49
|
+
title: "Suggest buyer questions",
|
|
50
|
+
description: "Suggest buyer-intent questions to check for a product, from fixed templates. No model call and no cost. The product's own name never appears in a question. Show them to the user to edit before starting a report.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
domain: z.string().min(3).describe("The product's domain, e.g. example.com"),
|
|
53
|
+
category: z.string().min(2).describe("What buyers search for, e.g. meeting scheduling"),
|
|
54
|
+
description: z.string().max(300).optional().describe("One line on what the product does"),
|
|
55
|
+
count: z.number().int().min(1).max(12).optional(),
|
|
56
|
+
},
|
|
57
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
58
|
+
}, async (input) => ok({ prompts: suggestPrompts(input) }));
|
|
59
|
+
server.registerTool("recomention_report_start", {
|
|
60
|
+
title: "Start a report (spends a credit or your provider keys)",
|
|
61
|
+
description: "Start a recomention report: the questions are asked of every engine on the plan several times each, with live web search. " +
|
|
62
|
+
"THIS SPENDS MONEY: funding \"credits\" uses one agency credit from the user's account (plan must be FULL); funding \"byok\" runs on the user's own provider keys from this server's environment (OPENAI_API_KEY, ANTHROPIC_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, XAI_API_KEY, DATAFORSEO_API_KEY as login:password for Google AI Overview) and needs an active Agent plan on the account. " +
|
|
63
|
+
"Any subset of keys works and engines without a key are reported as skipped, but one of OpenAI, Anthropic or Gemini is required. A refusal names the plan limit it hit. " +
|
|
64
|
+
"Always ask the user before calling it. Reports take several minutes: poll recomention_report_status about once a minute.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
domain: z.string().min(3),
|
|
67
|
+
description: z.string().min(3).max(300),
|
|
68
|
+
category: z.string().min(2).max(120),
|
|
69
|
+
brand: z.string().max(80).optional().describe("Defaults to the name in the domain"),
|
|
70
|
+
plan: z.enum(["QUICK", "FULL", "DEEP"]).default("FULL"),
|
|
71
|
+
prompts: z.array(z.string().min(8).max(200)).max(40).optional().describe("Questions to ask; the rest are written for you. None may name the product."),
|
|
72
|
+
engines: z.array(z.enum(PROVIDERS)).min(1).optional().describe("A subset of the plan's engines"),
|
|
73
|
+
funding: z.enum(["credits", "byok"]),
|
|
74
|
+
idempotencyKey: z.string().regex(/^[A-Za-z0-9_-]{16,128}$/).optional().describe("Reuse to make a retried start land on the same report"),
|
|
75
|
+
},
|
|
76
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
77
|
+
}, ({ prompts, funding, ...rest }) => run(async () => {
|
|
78
|
+
const providerKeys = funding === "byok" ? providerKeysFromEnv(env) : undefined;
|
|
79
|
+
if (funding === "byok" && !Object.keys(providerKeys ?? {}).length) {
|
|
80
|
+
throw new Error("funding \"byok\" needs at least one provider key in this MCP server's env (OPENAI_API_KEY, ANTHROPIC_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, XAI_API_KEY or DATAFORSEO_API_KEY).");
|
|
81
|
+
}
|
|
82
|
+
// The keys go to the API in the request body and nowhere else: never into the tool result.
|
|
83
|
+
const started = await api.start({ ...rest, funding, ...(prompts?.length ? { seedPrompts: prompts } : {}), ...(providerKeys ? { providerKeys } : {}) });
|
|
84
|
+
return { ...started, next: "Poll recomention_report_status with this slug about once a minute." };
|
|
85
|
+
}));
|
|
86
|
+
server.registerTool("recomention_report_status", {
|
|
87
|
+
title: "Report status",
|
|
88
|
+
description: "Where a report is: PENDING, RUNNING, COMPLETE or FAILED, with answers stored so far. Free.",
|
|
89
|
+
inputSchema: { slug },
|
|
90
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
91
|
+
}, ({ slug }) => run(() => api.status(slug)));
|
|
92
|
+
server.registerTool("recomention_report_get", {
|
|
93
|
+
title: "Get a report",
|
|
94
|
+
description: "A report as JSON: the overall score, each engine's runs mentioned of runs total, competitors named instead, cited sources, the fix list and every question with every answer. " +
|
|
95
|
+
"Engines in enginesSkipped did not run; never describe them as not mentioning the product. simulated: true means test data. Free.",
|
|
96
|
+
inputSchema: { slug },
|
|
97
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
98
|
+
}, ({ slug }) => run(() => api.report(slug)));
|
|
99
|
+
server.registerTool("recomention_distribution_kit", {
|
|
100
|
+
title: "Get the distribution kit",
|
|
101
|
+
description: "Markdown files built from a finished report, for planning distribution: product.md, competitors.md, ai-visibility.md, where-to-get-mentioned.md (third-party pages, subreddits and videos the engines cite for a rival and not for this product) and channel-plan.md (channels ranked by those citations, each with a first task). " +
|
|
102
|
+
"Read README.md first. Every count is \"n of m answers\"; nothing in it promises traffic. Show the user every draft before anything is posted. Free.",
|
|
103
|
+
inputSchema: { slug },
|
|
104
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
105
|
+
}, ({ slug }) => run(() => api.kit(slug)));
|
|
106
|
+
server.registerTool("recomention_reports_list", {
|
|
107
|
+
title: "List my reports",
|
|
108
|
+
description: "The reports in the account that owns the API key, newest first. Free.",
|
|
109
|
+
inputSchema: {},
|
|
110
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
111
|
+
}, () => run(() => api.reports()));
|
|
112
|
+
return server;
|
|
113
|
+
}
|
package/dist/suggest.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer questions for a category, from fixed templates: no model call, no
|
|
3
|
+
* network, no cost, and the same input always gives the same questions.
|
|
4
|
+
*
|
|
5
|
+
* The product's own name never appears in a question — the point of a check
|
|
6
|
+
* is whether the engines name it unprompted — so anything that would contain
|
|
7
|
+
* it is dropped.
|
|
8
|
+
*/
|
|
9
|
+
const TEMPLATES = [
|
|
10
|
+
{ intent: "best_in_category", text: (c) => `what is the best ${c} tool right now?` },
|
|
11
|
+
{ intent: "recommendation", text: (c) => `which ${c} software would you recommend for a small team?` },
|
|
12
|
+
{ intent: "recommendation", text: (c) => `what ${c} tools do startups use in 2026?` },
|
|
13
|
+
{ intent: "use_case", text: (c, d) => (d ? `i need something for ${d}. what ${c} tool should i use?` : `what ${c} tool is easiest to set up?`) },
|
|
14
|
+
{ intent: "alternatives", text: (c) => `what are good alternatives to the most popular ${c} tools?` },
|
|
15
|
+
{ intent: "use_case", text: (c) => `best ${c} tool for a solo founder on a budget?` },
|
|
16
|
+
{ intent: "comparison", text: (c) => `compare the top 5 ${c} tools for features and price` },
|
|
17
|
+
{ intent: "use_case", text: (c) => `which ${c} tool works best for agencies with many clients?` },
|
|
18
|
+
{ intent: "recommendation", text: (c) => `what ${c} software has the best free plan?` },
|
|
19
|
+
{ intent: "use_case", text: (c) => `best ${c} tool for enterprise security and compliance?` },
|
|
20
|
+
{ intent: "comparison", text: (c) => `which ${c} tool has the best integrations?` },
|
|
21
|
+
{ intent: "best_in_category", text: (c) => `top rated ${c} apps according to reviews` },
|
|
22
|
+
];
|
|
23
|
+
function brandOf(domain) {
|
|
24
|
+
const host = domain.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0];
|
|
25
|
+
return host.split(".")[0] ?? host;
|
|
26
|
+
}
|
|
27
|
+
function clean(text) {
|
|
28
|
+
return text.replace(/\s+/g, " ").trim().toLowerCase();
|
|
29
|
+
}
|
|
30
|
+
export function suggestPrompts(input) {
|
|
31
|
+
const category = clean(input.category).replace(/\b(software|tools?|apps?)$/, "").trim() || clean(input.category);
|
|
32
|
+
const description = clean(input.description ?? "").replace(/[.?!]+$/, "");
|
|
33
|
+
const brand = brandOf(input.domain);
|
|
34
|
+
const count = Math.min(Math.max(input.count ?? 10, 1), TEMPLATES.length);
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const t of TEMPLATES) {
|
|
38
|
+
const text = clean(t.text(category, description));
|
|
39
|
+
if ((brand.length >= 3 && text.includes(brand)) || seen.has(text))
|
|
40
|
+
continue;
|
|
41
|
+
seen.add(text);
|
|
42
|
+
out.push({ text, intent: t.intent });
|
|
43
|
+
if (out.length === count)
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recomention/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for recomention: ask whether AI engines recommend a product, from Claude Code, Cursor or Codex.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"mcpName": "io.github.recomention/mcp",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"mcp",
|
|
10
|
+
"model-context-protocol",
|
|
11
|
+
"ai-visibility",
|
|
12
|
+
"llm",
|
|
13
|
+
"claude-code",
|
|
14
|
+
"cursor",
|
|
15
|
+
"codex"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://www.recomention.com/developers",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/recomention/mcp.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/recomention/mcp/issues",
|
|
24
|
+
"email": "support@recomention.com"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"recomention-mcp": "dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"!dist/**/*.test.js",
|
|
32
|
+
"!dist/**/*.map",
|
|
33
|
+
"skills",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"registry": "https://registry.npmjs.org/"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
45
|
+
"zod": "^4.4.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^26.2.0",
|
|
49
|
+
"tsx": "^4.23.12",
|
|
50
|
+
"typescript": "^5",
|
|
51
|
+
"@recomention/tsconfig": "0.1.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"dev": "tsx src/index.ts",
|
|
55
|
+
"start": "tsx src/index.ts",
|
|
56
|
+
"clean": "node scripts/clean-dist.mjs",
|
|
57
|
+
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"test": "tsx --test src/*.test.ts",
|
|
60
|
+
"smoke": "tsx scripts/smoke.ts",
|
|
61
|
+
"smoke:pack": "tsx scripts/pack-smoke.ts"
|
|
62
|
+
}
|
|
6
63
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-visibility-check
|
|
3
|
+
description: Check whether AI engines (ChatGPT, Claude, Perplexity, Gemini, Grok) recommend a product, using the recomention MCP server. Use when the user asks "is my product recommended by ChatGPT/AI for <category>?", wants an AI visibility or AI search check, or names a domain and a category to check.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AI visibility check
|
|
7
|
+
|
|
8
|
+
Uses the `recomention_*` MCP tools. Starting a report spends money (an agency credit, or calls on the
|
|
9
|
+
user's own provider keys), so this skill always asks before it starts one.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. **Collect the inputs.** You need the product's domain, the category buyers search for (e.g.
|
|
14
|
+
"meeting scheduling"), and one line on what it does. Ask for whatever is missing; read it from the
|
|
15
|
+
repository's README or landing page if the user is working in the product's own repo, and confirm.
|
|
16
|
+
2. **Look for free evidence first.** Call `recomention_rankings_list`. If a public ranking matches the
|
|
17
|
+
category, call `recomention_rankings_get` and tell the user where the product appears (as "named in
|
|
18
|
+
n of m answers"), or that it is absent. This costs nothing.
|
|
19
|
+
3. **Draft the questions.** Call `recomention_suggest_prompts` and show the list. Let the user edit,
|
|
20
|
+
add or remove questions. None may contain the product's name — a question that names it measures
|
|
21
|
+
nothing. Remove any that do and say why.
|
|
22
|
+
4. **Confirm spend, then start.** Say what will run: plan (credits run `FULL`), the engines, the
|
|
23
|
+
funding (`credits` or `byok`). Only after the user agrees, call `recomention_report_start` with the
|
|
24
|
+
edited questions as `prompts` and a fresh `idempotencyKey` (random, 16+ characters). Reuse that key
|
|
25
|
+
if you have to retry the same start.
|
|
26
|
+
5. **Wait.** Poll `recomention_report_status` about once a minute and report progress
|
|
27
|
+
("answers stored / planned"). Do not start a second report for the same product meanwhile.
|
|
28
|
+
6. **Read it out.** When COMPLETE, call `recomention_report_get` and summarise:
|
|
29
|
+
- the overall score (0–100) and, per engine, `runsMentioned` of `runsTotal` answers;
|
|
30
|
+
- the top competitors named instead (share of answers);
|
|
31
|
+
- the sources the engines cited most, and whether the product is on them;
|
|
32
|
+
- the top three items of the fix list;
|
|
33
|
+
- the report link (`url`) for the full detail.
|
|
34
|
+
|
|
35
|
+
## Honesty rules
|
|
36
|
+
|
|
37
|
+
- Rates, never yes/no: "ChatGPT named it in 4 of 9 answers", not "ChatGPT recommends it".
|
|
38
|
+
- An engine in `enginesSkipped` did not run. Give its reason (e.g. "provider rejected the key"). Never
|
|
39
|
+
call it an engine that did not mention the product, and never count it as zero.
|
|
40
|
+
- An answer with `failed: true` was excluded from every count; do not treat it as "not mentioned".
|
|
41
|
+
- `simulated: true` means test data with made-up answers. Say so first, and draw no conclusions.
|
|
42
|
+
- Every report is a dated snapshot. Say the date when you quote numbers.
|
|
43
|
+
- If the report FAILED, say so and show its `error`. Credit-funded reports that fail get their credit
|
|
44
|
+
back automatically; do not retry more than once without asking.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: competitor-watch
|
|
3
|
+
description: See which products AI engines name in a category and how a product compares with its competitors, from recomention's public rankings and the user's own reports. Use when the user asks who ChatGPT/AI recommends for a category, how they compare with a competitor in AI answers, or whether that changed since the last check.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Competitor watch
|
|
7
|
+
|
|
8
|
+
Uses the `recomention_*` MCP tools. Everything here is read-only and free unless the user asks for a
|
|
9
|
+
new report.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. **Public rankings first.** Call `recomention_rankings_list`; for each ranking that matches the
|
|
14
|
+
category, call `recomention_rankings_get`. Present the ranked products as "named in n of m answers"
|
|
15
|
+
with the average position, per engine where it differs, and the date it was checked.
|
|
16
|
+
2. **The user's own reports.** Call `recomention_reports_list` and pick COMPLETE reports in the same
|
|
17
|
+
category (non-simulated). For each, `recomention_report_get` and read `competitors` (share of
|
|
18
|
+
answers, average rank) and per-engine `runsMentioned` of `runsTotal`.
|
|
19
|
+
3. **Compare like with like.** Only compare runs that asked the same questions (a re-check of the same
|
|
20
|
+
report does; two unrelated reports usually do not) — say so when they differ. Put the user's product
|
|
21
|
+
and its named competitors side by side: share of answers, engines that name each, average position.
|
|
22
|
+
4. **What changed.** If there are two or more comparable reports, list who moved up or down between the
|
|
23
|
+
dates, and by how many answers. Small moves (one or two answers) are within run-to-run variation;
|
|
24
|
+
call them that.
|
|
25
|
+
5. **Offer, don't start.** If nothing current covers the category, suggest a new report via the
|
|
26
|
+
`ai-visibility-check` skill — and say that starting one spends a credit or the user's provider keys.
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
- Rates, never yes/no. Always give the denominator and the date.
|
|
31
|
+
- Engines that were skipped or calls that failed are excluded, never counted as "not named".
|
|
32
|
+
- Simulated reports are test data: leave them out of any comparison.
|
|
33
|
+
- Name products only as the engines did; do not add your own opinion of which is better.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: distribution-kit
|
|
3
|
+
description: Turn a finished recomention report into distribution work: read its kit (product, competitors, AI visibility, where to get mentioned, channel plan) and work through the channel plan with the user, one channel at a time. Use when the user asks how to get their product recommended by AI engines, where to get mentioned, or what to do with a recomention report.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Distribution kit
|
|
7
|
+
|
|
8
|
+
Reads a finished report's Distribution Kit with the `recomention_*` MCP tools. Costs nothing: it only reads.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. **Load the kit.** Get the slug (the last path segment of a `/r/<slug>` link, or pick from
|
|
13
|
+
`recomention_reports_list`) and call `recomention_distribution_kit`. A 409 means the report is not
|
|
14
|
+
finished: call `recomention_report_status`, say where it is, and stop.
|
|
15
|
+
2. **Read every file, `README.md` first.** If the files say SIMULATED TEST DATA, tell the user and stop:
|
|
16
|
+
a test run's answers are made up.
|
|
17
|
+
3. **Summarise in five lines:** the score, each engine's "named in n of m answers", skipped engines and
|
|
18
|
+
why, the top rivals, and the top three channels from `channel-plan.md`.
|
|
19
|
+
4. **Work the channel plan in order, one channel at a time.** For each, take the pages for that channel
|
|
20
|
+
from `where-to-get-mentioned.md`, open them, and confirm they are still relevant before suggesting
|
|
21
|
+
anything. Then draft the first task (a reply, a pitch email, a review request, a page on the user's
|
|
22
|
+
own site) and show it to the user. Nothing is posted or sent without their yes.
|
|
23
|
+
5. **Close the loop.** Suggest a new report in about a month with the same questions, to see whether
|
|
24
|
+
the engines' answers changed.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
- Be openly the product's maker everywhere: no fake reviews, no posing as a customer, no paid placement
|
|
29
|
+
dressed as editorial, no vote manipulation.
|
|
30
|
+
- The kit counts citations; it did not read the pages. Check a page before acting on it.
|
|
31
|
+
- Quote counts as "n of m". An engine listed as skipped did not run; it is not a zero.
|
|
32
|
+
- Never promise traffic or rankings.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fix-list-review
|
|
3
|
+
description: Turn a finished recomention report into a prioritised, honest plan of work — what to change on the site, which third-party pages matter, and what to re-measure. Use when the user has a recomention report slug or link and asks what to do about it, how to improve their AI visibility, or to review the fix list.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fix list review
|
|
7
|
+
|
|
8
|
+
Reads a finished report with the `recomention_*` MCP tools. Costs nothing: it only reads.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. **Load the report.** Get the slug (the last path segment of a `/r/<slug>` link, or pick from
|
|
13
|
+
`recomention_reports_list`). Call `recomention_report_get`. If `status` is not COMPLETE, call
|
|
14
|
+
`recomention_report_status`, say where it is, and stop.
|
|
15
|
+
2. **State the baseline** in one short paragraph: overall score, per-engine `runsMentioned` of
|
|
16
|
+
`runsTotal`, skipped engines with their reasons, the check date, and whether it is simulated.
|
|
17
|
+
3. **Walk the fix list** (`actionPlan`) in order. For each item give: what to do, why the report
|
|
18
|
+
suggests it (point at the evidence: a competitor's share of answers, a cited source the product is
|
|
19
|
+
missing from, a question where it never appears), and the effort.
|
|
20
|
+
4. **Ground it in the answers.** For the two or three questions where the product is absent most
|
|
21
|
+
often, quote one short excerpt of an engine answer and the products it named instead. Use
|
|
22
|
+
`questions[].answers[]`; skip answers with `failed: true`.
|
|
23
|
+
5. **Separate what the user controls from what they don't.** Their own site and docs: concrete edits
|
|
24
|
+
they can make now (if you are in the product's repository, offer to draft them). Third-party pages
|
|
25
|
+
(`sources` the engines cite): who to contact and what an honest inclusion request looks like.
|
|
26
|
+
6. **Say how to know it worked.** A later report with the same questions shows the change; suggest
|
|
27
|
+
re-checking in about a month rather than days, since engines' answers move slowly.
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
- Never suggest fake reviews, paid placements dressed as editorial, posing as a customer, or any
|
|
32
|
+
other way of presenting the product as an unrelated third party. Community threads: answering
|
|
33
|
+
openly as the founder is the honest option.
|
|
34
|
+
- Do not promise ranking outcomes. The report measures what engines said and where they looked.
|
|
35
|
+
- Quote counts as "n of m". An engine in `enginesSkipped` is not a zero.
|
|
36
|
+
- A simulated report has made-up answers: do not build a plan on it.
|