@recomention/mcp 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -77,7 +77,7 @@ env = { RECOMENTION_API_KEY = "<key>", RECOMENTION_BASE_URL = "https://api.recom
77
77
  | `recomention_suggest_prompts({domain, category, description})` | free | Buyer questions from fixed templates. No model call. Never names the product. |
78
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
79
  | `recomention_report_status(slug)` | free | PENDING / RUNNING / COMPLETE / FAILED, with progress. |
80
- | `recomention_report_get(slug)` | free | The full report JSON. |
80
+ | `recomention_report_get(slug, question?)` | free | The report without answer texts, with per-question counts numbered from 1. Pass `question` (1-based: 1 is the first) for that question's answers and citations. `0` is deprecated and also means the first. |
81
81
  | `recomention_distribution_kit(slug)` | free | Markdown files built from a finished report: product, competitors, AI visibility, where to get mentioned, channel plan. |
82
82
  | `recomention_reports_list` | free | Your reports, newest first. |
83
83
 
package/dist/report.js ADDED
@@ -0,0 +1,61 @@
1
+ const usable = (a) => !a.failed && !a.noAnswer && (a.mentioned === true || a.mentioned === false);
2
+ export function questionSummary(q, number) {
3
+ const answers = Array.isArray(q.answers) ? q.answers : [];
4
+ const byEngine = new Map();
5
+ for (const a of answers) {
6
+ const e = byEngine.get(String(a.engine)) ?? { named: 0, usable: 0, notUsable: 0 };
7
+ if (usable(a)) {
8
+ e.usable++;
9
+ if (a.mentioned === true)
10
+ e.named++;
11
+ }
12
+ else
13
+ e.notUsable++;
14
+ byEngine.set(String(a.engine), e);
15
+ }
16
+ const named = [...byEngine.values()].reduce((n, e) => n + e.named, 0);
17
+ const of = [...byEngine.values()].reduce((n, e) => n + e.usable, 0);
18
+ return {
19
+ question: number, text: q.text, intent: q.intent, stage: q.stage,
20
+ named: `${named} of ${of} usable answers`,
21
+ engines: Object.fromEntries([...byEngine].map(([engine, e]) => [engine, { named: `${e.named} of ${e.usable}`, notUsable: e.notUsable }])),
22
+ };
23
+ }
24
+ /**
25
+ * The agent asked for a question the report does not have. Thrown, not returned: the
26
+ * API answered 200, so a returned { error } would reach the agent as a successful result.
27
+ * Both servers turn it into a tool error (isError: true) carrying this message.
28
+ */
29
+ export class QuestionNotFoundError extends Error {
30
+ name = "QuestionNotFoundError";
31
+ }
32
+ const ZERO_IS_DEPRECATED = "question 0 is deprecated: questions are numbered from 1, and 0 reads as 1, the first question.";
33
+ export function compactReport(body, question) {
34
+ const report = body?.report;
35
+ if (!report || !Array.isArray(report.questions))
36
+ return body;
37
+ const questions = report.questions;
38
+ if (question !== undefined) {
39
+ // 1-based: question n is the n-th in the report's order. The deprecated 0 also means the first.
40
+ const index = question === 0 ? 0 : question - 1;
41
+ const match = Number.isInteger(index) && index >= 0 ? questions[index] : undefined;
42
+ if (!match)
43
+ throw new QuestionNotFoundError(questions.length ? `No question ${question}; questions are numbered 1 to ${questions.length}.` : `No question ${question}; this report has no questions.`);
44
+ const { order: _storedOrder, ...rest } = match;
45
+ return {
46
+ report: {
47
+ slug: report.slug, brand: report.brand, domain: report.domain, simulated: report.simulated, enginesSkipped: report.enginesSkipped,
48
+ question: { question: index + 1, ...rest },
49
+ ...(question === 0 ? { numbering: ZERO_IS_DEPRECATED } : {}),
50
+ },
51
+ };
52
+ }
53
+ return {
54
+ ...body,
55
+ report: {
56
+ ...report,
57
+ questions: questions.map((q, i) => questionSummary(q, i + 1)),
58
+ answersOmitted: `Answer texts, brands and citations are left out to fit an agent's context. Call again with question set to a question's number (1 is the first) for its answers. Engines in enginesSkipped did not run.`,
59
+ },
60
+ };
61
+ }
package/dist/server.js CHANGED
@@ -3,6 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { z } from "zod";
4
4
  import { ApiError, PROVIDERS, providerKeysFromEnv } from "./api.js";
5
5
  import { suggestPrompts } from "./suggest.js";
6
+ import { compactReport } from "./report.js";
6
7
  export const SERVER_NAME = "recomention";
7
8
  /**
8
9
  * package.json's version, read at startup: one number for npm, the MCP handshake and the
@@ -26,6 +27,9 @@ async function run(fn) {
26
27
  }
27
28
  }
28
29
  const slug = z.string().min(1).max(100).describe("The slug from a previous call.");
30
+ /** Kept in step with core's application/hosted-mcp.ts; this package ships without core. */
31
+ const QUESTION_NUMBER = "1-based question number, as the summary shows it: 1 is the first question, n the n-th. " +
32
+ "0 is deprecated and also reads as the first question (older agents counted from 0), so 0 and 1 return the same question.";
29
33
  /**
30
34
  * The recomention tools. Read tools are free. `recomention_report_start` is the
31
35
  * only tool that spends anything (an agency credit, or calls on the user's own
@@ -91,11 +95,12 @@ export function createServer(api, env = process.env) {
91
95
  }, ({ slug }) => run(() => api.status(slug)));
92
96
  server.registerTool("recomention_report_get", {
93
97
  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. " +
98
+ 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, per question, how many usable answers named the product. " +
99
+ "Answer texts are left out to fit your context: pass question (1-based: 1 is the first question, the number each summary entry shows) to read that question's answers and citations. " +
95
100
  "Engines in enginesSkipped did not run; never describe them as not mentioning the product. simulated: true means test data. Free.",
96
- inputSchema: { slug },
101
+ inputSchema: { slug, question: z.number().int().min(0).max(200).optional().describe(QUESTION_NUMBER) },
97
102
  annotations: { readOnlyHint: true, openWorldHint: true },
98
- }, ({ slug }) => run(() => api.report(slug)));
103
+ }, ({ slug, question }) => run(async () => compactReport((await api.report(slug)), question)));
99
104
  server.registerTool("recomention_distribution_kit", {
100
105
  title: "Get the distribution kit",
101
106
  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). " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recomention/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for recomention: ask whether AI engines recommend a product, from Claude Code, Cursor or Codex.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@modelcontextprotocol/sdk": "^1.32.1",
45
- "zod": "^4.4.3"
45
+ "zod": "^4.6.7"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^26.2.0",
@@ -18,8 +18,9 @@ Reads a finished report with the `recomention_*` MCP tools. Costs nothing: it on
18
18
  suggests it (point at the evidence: a competitor's share of answers, a cited source the product is
19
19
  missing from, a question where it never appears), and the effort.
20
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`.
21
+ often, quote one short excerpt of an engine answer and the products it named instead. Call
22
+ `recomention_report_get` again with `question` set to that question's number (1 is the first) for
23
+ its answers; skip answers with `failed: true`.
23
24
  5. **Separate what the user controls from what they don't.** Their own site and docs: concrete edits
24
25
  they can make now (if you are in the product's repository, offer to draft them). Third-party pages
25
26
  (`sources` the engines cite): who to contact and what an honest inclusion request looks like.