@recomention/mcp 0.1.0 → 0.1.1
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/dist/report.js +46 -0
- package/dist/server.js +5 -3
- package/package.json +1 -1
package/dist/report.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const usable = (a) => !a.failed && !a.noAnswer && (a.mentioned === true || a.mentioned === false);
|
|
2
|
+
export function questionSummary(q) {
|
|
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
|
+
order: q.order, 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
|
+
export function compactReport(body, question) {
|
|
25
|
+
const report = body?.report;
|
|
26
|
+
if (!report || !Array.isArray(report.questions))
|
|
27
|
+
return body;
|
|
28
|
+
const questions = report.questions;
|
|
29
|
+
if (question !== undefined) {
|
|
30
|
+
// By the question's own order (real reports number from 0), never by list position.
|
|
31
|
+
const match = questions.find((q) => q.order === question);
|
|
32
|
+
if (!match) {
|
|
33
|
+
const orders = questions.map((q) => Number(q.order)).filter(Number.isFinite);
|
|
34
|
+
return { error: orders.length ? `No question with order ${question}; orders run ${Math.min(...orders)} to ${Math.max(...orders)}.` : `No question with order ${question}.` };
|
|
35
|
+
}
|
|
36
|
+
return { report: { slug: report.slug, brand: report.brand, domain: report.domain, simulated: report.simulated, enginesSkipped: report.enginesSkipped, question: match } };
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
...body,
|
|
40
|
+
report: {
|
|
41
|
+
...report,
|
|
42
|
+
questions: questions.map(questionSummary),
|
|
43
|
+
answersOmitted: `Answer texts, brands and citations are left out to fit an agent's context. Call again with question set to a question's order (from 0) for its answers. Engines in enginesSkipped did not run.`,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
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
|
|
@@ -91,11 +92,12 @@ export function createServer(api, env = process.env) {
|
|
|
91
92
|
}, ({ slug }) => run(() => api.status(slug)));
|
|
92
93
|
server.registerTool("recomention_report_get", {
|
|
93
94
|
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
|
|
95
|
+
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. " +
|
|
96
|
+
"Answer texts are left out to fit your context: pass question (its order, from 0) to read that question's answers and citations. " +
|
|
95
97
|
"Engines in enginesSkipped did not run; never describe them as not mentioning the product. simulated: true means test data. Free.",
|
|
96
|
-
inputSchema: { slug },
|
|
98
|
+
inputSchema: { slug, question: z.number().int().min(0).max(200).optional().describe("A question's order number, to read its answers.") },
|
|
97
99
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
98
|
-
}, ({ slug }) => run(() => api.report(slug)));
|
|
100
|
+
}, ({ slug, question }) => run(async () => compactReport((await api.report(slug)), question)));
|
|
99
101
|
server.registerTool("recomention_distribution_kit", {
|
|
100
102
|
title: "Get the distribution kit",
|
|
101
103
|
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