@cite42/mcp 0.6.2 → 0.8.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/README.md +21 -17
- package/dist/stdio.cjs +74 -95
- package/dist/stdio.mjs +74 -95
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ Cite42 exposes 29 MCP tools: 4 free account tools (including the compatibility a
|
|
|
138
138
|
### Account & pricing (free)
|
|
139
139
|
|
|
140
140
|
- `cite42_balance`: check the connected account’s remaining Cite42 balance in USD.
|
|
141
|
-
- `cite42_credits`:
|
|
141
|
+
- `cite42_credits`: alias of `cite42_balance` for older integrations.
|
|
142
142
|
- `cite42_pricing`: get current per-tool prices and estimate a planned set of calls.
|
|
143
143
|
- `cite42_usage`: read recent call status and cost together with the remaining balance.
|
|
144
144
|
|
|
@@ -149,14 +149,14 @@ Cite42 exposes 29 MCP tools: 4 free account tools (including the compatibility a
|
|
|
149
149
|
- `cite42_rankings`: measure how brands rank in AI answers, including mention rate, average position, and per-model breakdown.
|
|
150
150
|
- `cite42_compare`: compare one brand against competitors in AI answers.
|
|
151
151
|
- `cite42_sentiment`: score positive, neutral, and negative sentiment for a brand, with supporting phrases.
|
|
152
|
-
- `cite42_recommendations`:
|
|
152
|
+
- `cite42_recommendations`: page-level recommendations. A page URL alone gets a free technical check with suggested buyer questions; with a buyer question it measures citations and compares your page with up to two pages the AI surfaces cited, charged only when it includes verified page-comparison advice (plus the citations check it ran).
|
|
153
153
|
|
|
154
154
|
### SEO & trend data
|
|
155
155
|
|
|
156
156
|
- `cite42_keywords`: get seed search volume, CPC, and competition, with optional related keyword ideas.
|
|
157
157
|
- `cite42_keywords_batch`: check volume, CPC, and competition for two to 50 seed keywords in one request; billed by outcome (call `cite42_pricing` for rates).
|
|
158
158
|
- `cite42_trends`: get Google Trends interest over time, related and rising queries, and a trend label.
|
|
159
|
-
- `cite42_reddit_trends`: find relevant Reddit discussions and
|
|
159
|
+
- `cite42_reddit_trends`: find relevant Reddit discussions with cited summaries and read up to three pages through Gemini URL context. Returns page titles, authors, post excerpts, relative ages, available scores/comment counts and sampled human comments. Pages may be cached and model extraction is not independently verified. Exact dates, growth and quote-based insight arrays remain unavailable. Read `relevantThreads`; `risingThreads` is a deprecated compatibility alias. Check `coverage` and `warnings` for missing fields and out-of-window threads.
|
|
160
160
|
- `cite42_youtube_trends`: find YouTube rising videos, creator angles, title patterns, and opportunities.
|
|
161
161
|
|
|
162
162
|
### Research workflows (multi-source, one call)
|
|
@@ -213,17 +213,15 @@ smoke test against a loopback fake API. See [local MCP testing](../../docs/mcp-t
|
|
|
213
213
|
- Docs: [Cite42 API & MCP documentation](https://www.cite42.dev/docs)
|
|
214
214
|
- Pricing: [Cite42 pay-per-call pricing](https://www.cite42.dev/pricing)
|
|
215
215
|
|
|
216
|
-
### USD billing
|
|
216
|
+
### USD billing
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
Its `chargedUsd` and `remainingUsd` values are decimal strings in USD.
|
|
220
|
-
|
|
218
|
+
Every paid result carries a `cite42Billing` block with the amount charged and the remaining
|
|
219
|
+
Cite42 balance. Its `chargedUsd` and `remainingUsd` values are decimal strings in USD.
|
|
220
|
+
Credits are retired: since 0.8.0 the package no longer returns `cite42Credits`, and
|
|
221
|
+
`cite42_credits` answers exactly like `cite42_balance`.
|
|
221
222
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
REST clients can use `GET /v1/pricing?currency=USD`; the default REST response remains
|
|
225
|
-
compatible with existing installations. Update the MCP package to receive the new
|
|
226
|
-
USD guidance and `cite42_balance` tool. Tracker activation still accepts the exact
|
|
223
|
+
`cite42_pricing` always returns the USD price table; an older `currency` argument is ignored.
|
|
224
|
+
REST clients can use `GET /v1/pricing?currency=USD`. Tracker activation still accepts the exact
|
|
227
225
|
integer `maxCostMicroPerRun` returned by the API; present its USD equivalent to users.
|
|
228
226
|
|
|
229
227
|
## Optional AI visibility recommendations
|
|
@@ -245,11 +243,17 @@ the complete prompt/configuration set, methodology and complete coverage to matc
|
|
|
245
243
|
Individual prompts remain small samples (at most five surfaces); percentage-point changes are withheld
|
|
246
244
|
below 10 answers per group in each run. This guardrail does not establish statistical significance.
|
|
247
245
|
|
|
248
|
-
`cite42_recommendations` is a separate, optional
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
`cite42_recommendations` is a separate, optional report. With no saved result, `url` alone runs a free
|
|
247
|
+
technical check (robots.txt crawler access, indexing, rendering) and suggests buyer questions, and `url`
|
|
248
|
+
with `query` first runs a citations check, priced like `cite42_citations`. Otherwise use one `requestId`
|
|
249
|
+
returned by an earlier call, a user-owned `trackerRunId`, or a structured `evidence` array. Pass `url` for the page
|
|
250
|
+
that should answer the prompt, or `context.domain` to look for a match in that site's sitemap, and
|
|
251
|
+
`query` to choose a prompt when the evidence covers several. Optional `context` also accepts brand,
|
|
252
|
+
audience and goal for this request only. The report fetches that page, its robots.txt and up to two
|
|
253
|
+
pages the AI surfaces cited, as returned HTML without JavaScript. Check `cite42_pricing` for the
|
|
254
|
+
current report price and obtain the user's agreement before running the add-on. It is charged only
|
|
255
|
+
when the report includes page-comparison recommendations backed by quotes from the inspected pages;
|
|
256
|
+
everything else is free.
|
|
253
257
|
|
|
254
258
|
For rankings evidence, provide `context.brand` to identify the tracked brand you want advice for.
|
|
255
259
|
Without it, the measurements remain available but the tool asks which brand to focus on for free;
|
package/dist/stdio.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/client.ts
|
|
5
5
|
var DEFAULT_BASE_URL = "https://www.cite42.dev/api/v1";
|
|
6
|
-
var
|
|
6
|
+
var FUNDS_LOCATION = "the Cite42 dashboard at https://www.cite42.dev/app";
|
|
7
7
|
function toUsd(micro) {
|
|
8
8
|
const [whole, fraction = ""] = (micro / 1e5).toFixed(5).split(".");
|
|
9
9
|
return `${whole}.${fraction.slice(0, Math.max(2, fraction.replace(/0+$/, "").length))}`;
|
|
@@ -20,16 +20,6 @@ function toCite42Balance(micro) {
|
|
|
20
20
|
function isJsonObject(value) {
|
|
21
21
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
22
|
}
|
|
23
|
-
function toCredits(microCredits) {
|
|
24
|
-
return microCredits / MICRO_CREDITS_PER_CREDIT;
|
|
25
|
-
}
|
|
26
|
-
function formatCredits(microCredits) {
|
|
27
|
-
const credits = toCredits(microCredits);
|
|
28
|
-
return credits.toLocaleString(
|
|
29
|
-
"en-US",
|
|
30
|
-
Number.isInteger(credits) ? { maximumFractionDigits: 0 } : { minimumFractionDigits: 0, maximumFractionDigits: 2 }
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
23
|
function isTrackerCommitment(billing) {
|
|
34
24
|
return typeof billing.balanceMicro === "number" && typeof billing.costMicroPerRun === "number";
|
|
35
25
|
}
|
|
@@ -53,38 +43,17 @@ function toCite42McpResult(value) {
|
|
|
53
43
|
...replay ? { originalChargedUsd: toUsd(originalCost) } : {},
|
|
54
44
|
message: (replay ? `Replay of an identical request from the last 2 minutes. This attempt was free; $${toUsd(originalCost)} USD was charged once, by the original Cite42 call.` : `Cite42: $${toUsd(cost)} USD charged; $${toUsd(balance)} USD remaining.`) + lowBalanceNotice
|
|
55
45
|
};
|
|
56
|
-
|
|
57
|
-
const message = `Cite42 credits: ${formatCredits(cost)} charged; ${formatCredits(balance)} remaining.`;
|
|
58
|
-
return {
|
|
59
|
-
...result,
|
|
60
|
-
cite42Billing,
|
|
61
|
-
// Legacy numeric fields retain their original units for existing integrations.
|
|
62
|
-
cite42Credits: {
|
|
63
|
-
charged: toCredits(cost),
|
|
64
|
-
remaining: toCredits(balance),
|
|
65
|
-
...replay ? { originalCharged: toCredits(originalCost) } : {},
|
|
66
|
-
message: replay ? `Replay of an identical request from the last 2 minutes. This attempt was free; ${toCredits(originalCost)} credits were charged once, by the original call.` : lowCredits ? `${message} Low Cite42 credit balance.` : message
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function toCite42CreditsBalance(balanceMicroCredits) {
|
|
71
|
-
return {
|
|
72
|
-
...toCite42Balance(balanceMicroCredits),
|
|
73
|
-
cite42Credits: {
|
|
74
|
-
remaining: toCredits(balanceMicroCredits),
|
|
75
|
-
message: `Cite42 credits: ${formatCredits(balanceMicroCredits)} remaining. This balance check was free.`
|
|
76
|
-
}
|
|
77
|
-
};
|
|
46
|
+
return { ...result, cite42Billing };
|
|
78
47
|
}
|
|
79
48
|
function errorMessageForMcp(status, path, body) {
|
|
80
49
|
try {
|
|
81
50
|
const payload = JSON.parse(body);
|
|
82
51
|
if (isJsonObject(payload) && isJsonObject(payload.error) && typeof payload.error.code === "string") {
|
|
83
52
|
if (payload.error.code === "daily_limit_exceeded" || payload.error.code === "monthly_limit_exceeded") {
|
|
84
|
-
return
|
|
53
|
+
return `Cite42 spending limit reached. Change it in ${FUNDS_LOCATION}.`;
|
|
85
54
|
}
|
|
86
55
|
if (payload.error.code === "insufficient_credits") {
|
|
87
|
-
return
|
|
56
|
+
return `Insufficient Cite42 balance. Add funds in ${FUNDS_LOCATION}.`;
|
|
88
57
|
}
|
|
89
58
|
}
|
|
90
59
|
} catch {
|
|
@@ -176,18 +145,18 @@ var modelEnum = import_zod.z.enum(["chatgpt", "claude", "perplexity", "gemini",
|
|
|
176
145
|
var QUERY_MAX_CHARS = 2e3;
|
|
177
146
|
var SHORT_TEXT_MAX_CHARS = 200;
|
|
178
147
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
179
|
-
var MODEL_SELECTION_DESCRIPTION =
|
|
180
|
-
var FULL_SWEEP_DESCRIPTION = "Query all five AI surfaces
|
|
181
|
-
var
|
|
182
|
-
var
|
|
183
|
-
var
|
|
184
|
-
var KEYWORDS_PRICING = "
|
|
185
|
-
var WORKFLOW_PRICING = "
|
|
148
|
+
var MODEL_SELECTION_DESCRIPTION = "AI surfaces to query, each billed separately; pass a subset when the user names surfaces or wants a lower cost. Omit for the defaults (chatgpt, perplexity, gemini); fullSweep queries all five. google_ai_overview accepts queries up to 700 characters, the others 2000.";
|
|
149
|
+
var FULL_SWEEP_DESCRIPTION = "Query all five AI surfaces (adds claude and google_ai_overview) for full cross-surface coverage, at a higher cost. Ignored when models is given.";
|
|
150
|
+
var PAID_TOOL_BILLING = "Billed in USD (result field cite42Billing) to the user's prepaid Cite42 balance; exact prices from cite42_pricing (free). For plans over 20 calls or $5, estimate the cost and confirm with the user first.";
|
|
151
|
+
var RECOMMENDATIONS_INPUT_NOTE = "Its requestId can be passed to cite42_recommendations when the user asks for improvement advice.";
|
|
152
|
+
var MODEL_PRICING = "Priced per AI surface queried.";
|
|
153
|
+
var KEYWORDS_PRICING = "Billed by outcome: cheapest when the seed has no search volume, more when it has metrics, most with ideas:true.";
|
|
154
|
+
var WORKFLOW_PRICING = "Billed for the steps that succeed, at per-surface rates.";
|
|
186
155
|
function workflowDescription(description) {
|
|
187
|
-
return `${description} ${
|
|
156
|
+
return `${description} ${WORKFLOW_PRICING} ${PAID_TOOL_BILLING}`;
|
|
188
157
|
}
|
|
189
158
|
function readOnly(title, openWorldHint = true) {
|
|
190
|
-
return { annotations: { title, readOnlyHint: true, openWorldHint } };
|
|
159
|
+
return { annotations: { title, readOnlyHint: true, destructiveHint: false, openWorldHint } };
|
|
191
160
|
}
|
|
192
161
|
function mutation(title, options = {}) {
|
|
193
162
|
return {
|
|
@@ -209,7 +178,7 @@ function shortText(description) {
|
|
|
209
178
|
var aivRecommendations = (0, import_tools.createTool)({
|
|
210
179
|
id: "cite42_recommendations",
|
|
211
180
|
mcp: readOnly("Recommend AI visibility next steps"),
|
|
212
|
-
description: "
|
|
181
|
+
description: "Page-level AI visibility recommendations, for when the user asks how to improve a page. With a saved result (requestId, trackerRunId or evidence) it reports on one measured prompt, charged only when the report includes page-comparison advice backed by quotes from inspected pages. Without one: url alone runs a free technical check of the page (robots.txt crawler access, indexing, rendering) and returns suggested buyer questions; url with query first runs a citations check, priced like cite42_citations, then the report. Prices are in cite42_pricing. Fetches the page, its robots.txt and up to two pages the AI surfaces cited, as returned HTML without JavaScript. No site-wide crawl, keyword advice or tracker activation. " + PAID_TOOL_BILLING,
|
|
213
182
|
inputSchema: import_zod.z.object({
|
|
214
183
|
requestId: import_zod.z.string().trim().min(1).max(128).optional().describe("A requestId from your own successful Cite42 call within the last 30 days."),
|
|
215
184
|
trackerRunId: import_zod.z.string().trim().min(1).max(128).optional().describe("A specific finished run of your own tracker within the last 30 days."),
|
|
@@ -219,6 +188,9 @@ var aivRecommendations = (0, import_tools.createTool)({
|
|
|
219
188
|
result: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()),
|
|
220
189
|
measuredAt: import_zod.z.string().datetime()
|
|
221
190
|
})).min(1).max(20).optional(),
|
|
191
|
+
url: import_zod.z.string().trim().url().max(2e3).optional().describe("Your page that should answer the prompt. Without a saved result, url alone runs a free technical check; url with query runs a citations check first."),
|
|
192
|
+
query: import_zod.z.string().trim().min(1).max(2e3).optional().describe("A buyer question. With a saved result it picks one measured prompt; without one it is the question measured."),
|
|
193
|
+
models: import_zod.z.array(modelEnum).nonempty().optional().describe("AI surfaces for the citations check that runs without a saved result. Ignored with a saved result."),
|
|
222
194
|
context: import_zod.z.object({
|
|
223
195
|
brand: import_zod.z.string().trim().min(1).max(200).optional(),
|
|
224
196
|
domain: import_zod.z.string().trim().min(1).max(250).optional(),
|
|
@@ -226,8 +198,13 @@ var aivRecommendations = (0, import_tools.createTool)({
|
|
|
226
198
|
goal: import_zod.z.string().trim().min(1).max(400).optional()
|
|
227
199
|
}).optional()
|
|
228
200
|
}).superRefine((input, ctx) => {
|
|
229
|
-
|
|
230
|
-
|
|
201
|
+
const sources = [input.requestId, input.trackerRunId, input.evidence].filter((value) => value !== void 0).length;
|
|
202
|
+
if (sources > 1) ctx.addIssue({ code: "custom", message: "Provide at most one of requestId, trackerRunId or evidence." });
|
|
203
|
+
if (sources === 0 && !input.url) {
|
|
204
|
+
ctx.addIssue({ code: "custom", message: "Provide requestId, trackerRunId or evidence, or url for a page without saved results." });
|
|
205
|
+
}
|
|
206
|
+
if (sources === 0 && input.query && (input.url?.length ?? 0) > 500) {
|
|
207
|
+
ctx.addIssue({ code: "custom", message: "url must be at most 500 characters to run a citations check." });
|
|
231
208
|
}
|
|
232
209
|
if (new TextEncoder().encode(JSON.stringify(input)).length > 64 * 1024) {
|
|
233
210
|
ctx.addIssue({ code: "custom", message: "Recommendation input must fit within 64KB." });
|
|
@@ -241,60 +218,52 @@ function optionalShortText(description) {
|
|
|
241
218
|
var modelsInput = import_zod.z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
242
219
|
var fullSweepInput = import_zod.z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
|
|
243
220
|
var competitorList = import_zod.z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
221
|
+
async function fetchBalance() {
|
|
222
|
+
const usage = await aivGet("/usage");
|
|
223
|
+
const micro = usage.balance?.microCredits;
|
|
224
|
+
if (typeof micro !== "number" || !Number.isFinite(micro)) {
|
|
225
|
+
throw new Error("Cite42 usage response did not include a balance.");
|
|
226
|
+
}
|
|
227
|
+
return toCite42Balance(micro);
|
|
228
|
+
}
|
|
244
229
|
var aivBalance = (0, import_tools.createTool)({
|
|
245
230
|
id: "cite42_balance",
|
|
246
231
|
mcp: readOnly("Check Cite42 balance", false),
|
|
247
|
-
description: "Check the remaining Cite42 balance for the connected account. Free.
|
|
232
|
+
description: "Check the remaining Cite42 balance in USD for the connected account. Free.",
|
|
248
233
|
inputSchema: import_zod.z.object({}),
|
|
249
|
-
execute:
|
|
250
|
-
const usage = await aivGet("/usage");
|
|
251
|
-
const microCredits = usage.balance?.microCredits;
|
|
252
|
-
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
253
|
-
throw new Error("Cite42 usage response did not include a balance.");
|
|
254
|
-
}
|
|
255
|
-
return toCite42Balance(microCredits);
|
|
256
|
-
}
|
|
234
|
+
execute: fetchBalance
|
|
257
235
|
});
|
|
258
236
|
var aivCredits = (0, import_tools.createTool)({
|
|
259
237
|
id: "cite42_credits",
|
|
260
238
|
mcp: readOnly("Check Cite42 balance (legacy alias)", false),
|
|
261
|
-
description: "
|
|
239
|
+
description: "Alias of cite42_balance for older integrations. Free.",
|
|
262
240
|
inputSchema: import_zod.z.object({}),
|
|
263
|
-
execute:
|
|
264
|
-
const usage = await aivGet("/usage");
|
|
265
|
-
const microCredits = usage.balance?.microCredits;
|
|
266
|
-
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
267
|
-
throw new Error("Cite42 usage response did not include a balance.");
|
|
268
|
-
}
|
|
269
|
-
return toCite42CreditsBalance(microCredits);
|
|
270
|
-
}
|
|
241
|
+
execute: fetchBalance
|
|
271
242
|
});
|
|
272
243
|
var aivPricing = (0, import_tools.createTool)({
|
|
273
244
|
id: "cite42_pricing",
|
|
274
245
|
mcp: readOnly("Get Cite42 price table", false),
|
|
275
|
-
description:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
execute: async (input) => input.currency === "USD" ? aivGet("/pricing?currency=USD") : { ...await aivGet("/pricing"), usd: await aivGet("/pricing?currency=USD") }
|
|
246
|
+
description: "Get the exact Cite42 price table in USD: per-surface rates, flat-rate tools, workflow maximums, and which tools are free. Use it for cost questions and to estimate a plan before running it. Free.",
|
|
247
|
+
// No currency switch: prices are USD only. An older client's `currency` argument is ignored.
|
|
248
|
+
inputSchema: import_zod.z.object({}),
|
|
249
|
+
execute: async () => aivGet("/pricing?currency=USD")
|
|
280
250
|
});
|
|
281
251
|
var aivUsage = (0, import_tools.createTool)({
|
|
282
252
|
id: "cite42_usage",
|
|
283
253
|
mcp: readOnly("Get recent Cite42 usage", false),
|
|
284
|
-
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining balance in USD. Free.
|
|
254
|
+
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining balance in USD. Free.",
|
|
285
255
|
inputSchema: import_zod.z.object({}),
|
|
286
256
|
execute: async () => {
|
|
287
257
|
const usage = await aivGet("/usage");
|
|
288
|
-
const
|
|
289
|
-
if (typeof
|
|
258
|
+
const micro = usage.balance?.microCredits;
|
|
259
|
+
if (typeof micro !== "number" || !Number.isFinite(micro)) {
|
|
290
260
|
throw new Error("Cite42 usage response did not include a balance.");
|
|
291
261
|
}
|
|
292
262
|
return {
|
|
293
|
-
...
|
|
263
|
+
...toCite42Balance(micro),
|
|
294
264
|
recentCalls: (usage.recent ?? []).slice(0, 20).map((row) => ({
|
|
295
265
|
endpoint: row.endpoint,
|
|
296
266
|
status: row.status,
|
|
297
|
-
credits: typeof row.costMicro === "number" ? toCredits(row.costMicro) : void 0,
|
|
298
267
|
costUsd: typeof row.costMicro === "number" ? toUsd(row.costMicro) : void 0,
|
|
299
268
|
at: row.createdAt
|
|
300
269
|
}))
|
|
@@ -304,7 +273,7 @@ var aivUsage = (0, import_tools.createTool)({
|
|
|
304
273
|
var aivSearch = (0, import_tools.createTool)({
|
|
305
274
|
id: "cite42_search",
|
|
306
275
|
mcp: readOnly("Search AI models"),
|
|
307
|
-
description: `Run a query against AI models (ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews) and return each model's answer along with the sources it cited. ${MODEL_PRICING} ${
|
|
276
|
+
description: `Run a query against AI models (ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews) and return each model's answer along with the sources it cited. ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
308
277
|
inputSchema: import_zod.z.object({
|
|
309
278
|
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
310
279
|
models: modelsInput,
|
|
@@ -315,7 +284,7 @@ var aivSearch = (0, import_tools.createTool)({
|
|
|
315
284
|
var aivCitations = (0, import_tools.createTool)({
|
|
316
285
|
id: "cite42_citations",
|
|
317
286
|
mcp: readOnly("Find AI citations"),
|
|
318
|
-
description: `Find which sources a model's web search surfaces for a query. Aggregates cited URLs across models with per-model counts; optionally checks whether a specific URL is cited. Measures the retrieval corpus behind a prompt \u2014 which domains own your category in AI search, and where your content is missing \u2014 not the citation links a consumer chat UI renders for a logged-in user. ${MODEL_PRICING} ${
|
|
287
|
+
description: `Find which sources a model's web search surfaces for a query. Aggregates cited URLs across models with per-model counts; optionally checks whether a specific URL is cited. Measures the retrieval corpus behind a prompt \u2014 which domains own your category in AI search, and where your content is missing \u2014 not the citation links a consumer chat UI renders for a logged-in user. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
319
288
|
inputSchema: import_zod.z.object({
|
|
320
289
|
query: queryText("The concise query to look up cited sources for."),
|
|
321
290
|
url: import_zod.z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
|
|
@@ -327,7 +296,7 @@ var aivCitations = (0, import_tools.createTool)({
|
|
|
327
296
|
var aivRankings = (0, import_tools.createTool)({
|
|
328
297
|
id: "cite42_rankings",
|
|
329
298
|
mcp: readOnly("Measure AI brand rankings"),
|
|
330
|
-
description: `Measure how a set of brands/competitors rank in AI answers for a query: mention rate, average position, and a per-model breakdown across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews. ${MODEL_PRICING} ${
|
|
299
|
+
description: `Measure how a set of brands/competitors rank in AI answers for a query: mention rate, average position, and a per-model breakdown across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
331
300
|
inputSchema: import_zod.z.object({
|
|
332
301
|
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
333
302
|
competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
|
|
@@ -339,7 +308,7 @@ var aivRankings = (0, import_tools.createTool)({
|
|
|
339
308
|
var aivCompare = (0, import_tools.createTool)({
|
|
340
309
|
id: "cite42_compare",
|
|
341
310
|
mcp: readOnly("Compare brand against competitors"),
|
|
342
|
-
description: `Compare one primary brand head-to-head against a competitor set in AI answers: per-brand rankings plus a co-mention graph of which brands the models name together. ${MODEL_PRICING} ${
|
|
311
|
+
description: `Compare one primary brand head-to-head against a competitor set in AI answers: per-brand rankings plus a co-mention graph of which brands the models name together. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
343
312
|
inputSchema: import_zod.z.object({
|
|
344
313
|
query: queryText("The concise query to compare the brand and competitors on."),
|
|
345
314
|
brand: shortText("Your brand, ranked alongside the competitors."),
|
|
@@ -352,7 +321,7 @@ var aivCompare = (0, import_tools.createTool)({
|
|
|
352
321
|
var aivSentiment = (0, import_tools.createTool)({
|
|
353
322
|
id: "cite42_sentiment",
|
|
354
323
|
mcp: readOnly("Score brand sentiment in AI answers"),
|
|
355
|
-
description: `Score how each AI model talks about a brand for a query: a positive/neutral/negative split per model with the verbatim phrases driving the tone, plus an aggregate across models. ${MODEL_PRICING} ${
|
|
324
|
+
description: `Score how each AI model talks about a brand for a query: a positive/neutral/negative split per model with the verbatim phrases driving the tone, plus an aggregate across models. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
356
325
|
inputSchema: import_zod.z.object({
|
|
357
326
|
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
358
327
|
brand: shortText("The brand to score sentiment for in each model answer."),
|
|
@@ -364,7 +333,7 @@ var aivSentiment = (0, import_tools.createTool)({
|
|
|
364
333
|
var aivKeywords = (0, import_tools.createTool)({
|
|
365
334
|
id: "cite42_keywords",
|
|
366
335
|
mcp: readOnly("Get SEO keyword data"),
|
|
367
|
-
description: `Classic keyword data for a seed term. Omit ideas when the user only needs that seed's monthly search volume, CPC, or competition. Set ideas:true only when the user asks for related, alternative, or long-tail suggestions from provider candidates. Not a multi-model AI-search query. The reply echoes requestedKeyword and carries status "exact" or "no_data"; when the seed has no search volume, any related term that does appears under alternatives \u2014 never treat an alternative's metrics as the requested keyword's. ${KEYWORDS_PRICING} ${
|
|
336
|
+
description: `Classic keyword data for a seed term. Omit ideas when the user only needs that seed's monthly search volume, CPC, or competition. Set ideas:true only when the user asks for related, alternative, or long-tail suggestions from provider candidates. Not a multi-model AI-search query. The reply echoes requestedKeyword and carries status "exact" or "no_data"; when the seed has no search volume, any related term that does appears under alternatives \u2014 never treat an alternative's metrics as the requested keyword's. ${KEYWORDS_PRICING} ${PAID_TOOL_BILLING}`,
|
|
368
337
|
inputSchema: import_zod.z.object({
|
|
369
338
|
seed: queryText("Seed keyword or concise topic to fetch search volume, CPC, and competition for."),
|
|
370
339
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
@@ -380,7 +349,7 @@ var aivKeywords = (0, import_tools.createTool)({
|
|
|
380
349
|
var aivKeywordsBatch = (0, import_tools.createTool)({
|
|
381
350
|
id: "cite42_keywords_batch",
|
|
382
351
|
mcp: readOnly("Get batch SEO keyword data"),
|
|
383
|
-
description: `Check search volume, CPC, and competition for two to 50 distinct seed keywords in one request. No related ideas. Billed by outcome
|
|
352
|
+
description: `Check search volume, CPC, and competition for two to 50 distinct seed keywords in one request. No related ideas. Billed by outcome. ${PAID_TOOL_BILLING}`,
|
|
384
353
|
inputSchema: import_zod.z.object({
|
|
385
354
|
seeds: import_zod.z.array(import_zod.z.string().trim().min(1).max(80).refine(
|
|
386
355
|
(seed) => seed.split(/\s+/).length <= 10,
|
|
@@ -397,7 +366,7 @@ var aivKeywordsBatch = (0, import_tools.createTool)({
|
|
|
397
366
|
var aivTrends = (0, import_tools.createTool)({
|
|
398
367
|
id: "cite42_trends",
|
|
399
368
|
mcp: readOnly("Get Google Trends data"),
|
|
400
|
-
description: `Google Trends interest-over-time, related and rising queries, and a rising/falling/stable label for a term. A flat-priced data tool, not an AI-model query. ${
|
|
369
|
+
description: `Google Trends interest-over-time, related and rising queries, and a rising/falling/stable label for a term. A flat-priced data tool, not an AI-model query. ${PAID_TOOL_BILLING}`,
|
|
401
370
|
inputSchema: import_zod.z.object({
|
|
402
371
|
term: queryText("Search term or concise topic to pull Google Trends interest-over-time for."),
|
|
403
372
|
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
|
|
@@ -408,7 +377,7 @@ var aivTrends = (0, import_tools.createTool)({
|
|
|
408
377
|
var aivRedditTrends = (0, import_tools.createTool)({
|
|
409
378
|
id: "cite42_reddit_trends",
|
|
410
379
|
mcp: readOnly("Find Reddit discussions"),
|
|
411
|
-
description:
|
|
380
|
+
description: "Discover relevant Reddit discussions with Google Search and read up to three thread pages through Gemini URL context. Returns Gemini-read titles, authors, post excerpts, relative ages, available scores/comment counts and sampled human comments, plus cited model summaries. Check coverage and warnings: pages may be cached, extraction is not independently verified, and exact dates, growth and quote-based insights are unavailable. risingThreads is a deprecated alias of relevantThreads. " + PAID_TOOL_BILLING,
|
|
412
381
|
inputSchema: import_zod.z.object({
|
|
413
382
|
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
414
383
|
subreddits: import_zod.z.array(shortText("Subreddit name without r/.")).max(20).optional().describe("Optional subreddit names, without r/."),
|
|
@@ -419,7 +388,7 @@ var aivRedditTrends = (0, import_tools.createTool)({
|
|
|
419
388
|
var aivYoutubeTrends = (0, import_tools.createTool)({
|
|
420
389
|
id: "cite42_youtube_trends",
|
|
421
390
|
mcp: readOnly("Get YouTube trend data"),
|
|
422
|
-
description: `YouTube trend data for rising videos, creator angles, title patterns, channel examples, and video-market opportunities around a topic. ${
|
|
391
|
+
description: `YouTube trend data for rising videos, creator angles, title patterns, channel examples, and video-market opportunities around a topic. ${PAID_TOOL_BILLING}`,
|
|
423
392
|
inputSchema: import_zod.z.object({
|
|
424
393
|
topic: queryText("Topic, keyword, niche, or audience to research on YouTube."),
|
|
425
394
|
region: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country/region code. Defaults to "US".'),
|
|
@@ -440,7 +409,9 @@ var aivFindContentOpportunities = (0, import_tools.createTool)({
|
|
|
440
409
|
brand: optionalShortText("Optional brand/domain to account for."),
|
|
441
410
|
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
442
411
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
443
|
-
channels: import_zod.z.array(channelEnum).optional().describe("Optional data sources to include.")
|
|
412
|
+
channels: import_zod.z.array(channelEnum).optional().describe("Optional data sources to include."),
|
|
413
|
+
models: modelsInput,
|
|
414
|
+
fullSweep: fullSweepInput
|
|
444
415
|
}),
|
|
445
416
|
execute: async (input, context) => aivPost("/workflows/content-opportunities", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
446
417
|
});
|
|
@@ -453,7 +424,9 @@ var aivAnalyzeTopicDemand = (0, import_tools.createTool)({
|
|
|
453
424
|
inputSchema: import_zod.z.object({
|
|
454
425
|
topic: queryText("Topic, keyword, category, or audience demand to analyze."),
|
|
455
426
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
456
|
-
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
427
|
+
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".'),
|
|
428
|
+
models: modelsInput,
|
|
429
|
+
fullSweep: fullSweepInput
|
|
457
430
|
}),
|
|
458
431
|
execute: async (input, context) => aivPost("/workflows/topic-demand", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
459
432
|
});
|
|
@@ -461,7 +434,7 @@ var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
|
461
434
|
id: "cite42_map_ai_prompts",
|
|
462
435
|
mcp: readOnly("Map buyer prompts for AI search"),
|
|
463
436
|
description: workflowDescription(
|
|
464
|
-
"Run the AI-prompt-map workflow: research for a list of buyer prompts worth tracking, from AI search, SEO demand, trends, citations, Reddit, and YouTube data. It returns evidence and a suggested prompt, not a finished list
|
|
437
|
+
"Run the AI-prompt-map workflow: research for a list of buyer prompts worth tracking, from AI search, SEO demand, trends, citations, Reddit, and YouTube data. It returns evidence and a suggested prompt, not a finished list; prompts written from it can be tracked with cite42_tracker_create."
|
|
465
438
|
),
|
|
466
439
|
inputSchema: import_zod.z.object({
|
|
467
440
|
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
@@ -469,7 +442,9 @@ var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
|
469
442
|
brand: optionalShortText("Optional brand/domain to include in prompt tracking recommendations."),
|
|
470
443
|
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
471
444
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
472
|
-
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
445
|
+
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".'),
|
|
446
|
+
models: modelsInput,
|
|
447
|
+
fullSweep: fullSweepInput
|
|
473
448
|
}),
|
|
474
449
|
execute: async (input, context) => aivPost("/workflows/ai-prompt-map", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
475
450
|
});
|
|
@@ -483,7 +458,9 @@ var aivAnalyzeCompetitorContent = (0, import_tools.createTool)({
|
|
|
483
458
|
competitors: competitorList.describe("Competitor brands, domains, or channels to analyze."),
|
|
484
459
|
topic: import_zod.z.string().trim().min(1).max(QUERY_MAX_CHARS).optional().describe("Optional topic/category to constrain the analysis."),
|
|
485
460
|
brand: optionalShortText("Optional primary brand/domain to compare against."),
|
|
486
|
-
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
461
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
462
|
+
models: modelsInput,
|
|
463
|
+
fullSweep: fullSweepInput
|
|
487
464
|
}),
|
|
488
465
|
execute: async (input, context) => aivPost("/workflows/competitor-content", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
489
466
|
});
|
|
@@ -514,7 +491,9 @@ var aivGenerateContentBrief = (0, import_tools.createTool)({
|
|
|
514
491
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
515
492
|
brand: optionalShortText("Optional brand/domain the brief is for."),
|
|
516
493
|
goal: optionalShortText("Optional content goal, e.g. rank, earn citations, convert."),
|
|
517
|
-
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
494
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
495
|
+
models: modelsInput,
|
|
496
|
+
fullSweep: fullSweepInput
|
|
518
497
|
}),
|
|
519
498
|
execute: async (input, context) => aivPost("/workflows/content-brief", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
520
499
|
});
|
|
@@ -570,11 +549,11 @@ var trackerDefinitionFields = {
|
|
|
570
549
|
"AI surfaces to query for every prompt. Omit for the three defaults (chatgpt, perplexity, gemini), or list surfaces explicitly \u2014 including all five \u2014 to fix the recurring cost."
|
|
571
550
|
)
|
|
572
551
|
};
|
|
573
|
-
var TRACKER_BILLING_DESCRIPTION = "
|
|
552
|
+
var TRACKER_BILLING_DESCRIPTION = "Managing trackers and reading reports is free; each run bills one call per prompt at the per-surface rates.";
|
|
574
553
|
var aivTrackerCreate = (0, import_tools.createTool)({
|
|
575
554
|
id: "cite42_tracker_create",
|
|
576
555
|
mcp: mutation("Create Cite42 tracker draft"),
|
|
577
|
-
description: "Create a draft weekly or monthly tracker: one measurement \u2014 rankings, compare, citations, or sentiment \u2014 applied to a list of prompts, with the brand, competitors and AI surfaces set once for all of them.
|
|
556
|
+
description: "Create a draft weekly or monthly tracker: one measurement \u2014 rankings, compare, citations, or sentiment \u2014 applied to a list of prompts, with the brand, competitors and AI surfaces set once for all of them. Prompt lists can come from cite42_map_ai_prompts research. Creation does not start recurring calls. " + TRACKER_BILLING_DESCRIPTION,
|
|
578
557
|
inputSchema: import_zod.z.object(trackerDefinitionFields),
|
|
579
558
|
execute: async (input) => aivPost("/trackers", input)
|
|
580
559
|
});
|
|
@@ -591,7 +570,7 @@ var aivTrackerActivate = (0, import_tools.createTool)({
|
|
|
591
570
|
openWorldHint: true,
|
|
592
571
|
idempotentHint: true
|
|
593
572
|
}),
|
|
594
|
-
description: `Activate or resume recurring weekly or monthly tool calls. maxCostMicroPerRun must equal the tracker
|
|
573
|
+
description: `Activate or resume recurring weekly or monthly tool calls. maxCostMicroPerRun must equal the tracker's latest estimatedCostMicroPerRun; activation authorizes future billed calls until paused. The response includes the balance, the cost per run, and runsDryAt, the date the balance stops covering active trackers. When tracker emails are enabled, activation sends a setup summary and every completed run sends current results and suggested next steps, even when nothing changed. Activation is refused when the balance cannot cover the next run. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
595
574
|
inputSchema: import_zod.z.object({
|
|
596
575
|
trackerId: trackerIdInput,
|
|
597
576
|
maxCostMicroPerRun: import_zod.z.number().int().nonnegative().describe("Exact estimatedCostMicroPerRun returned by the latest tracker record.")
|
|
@@ -695,7 +674,7 @@ if (!resolveApiKey()) {
|
|
|
695
674
|
);
|
|
696
675
|
process.exit(1);
|
|
697
676
|
}
|
|
698
|
-
buildCite42Server("0.
|
|
677
|
+
buildCite42Server("0.8.0").startStdio().catch((error) => {
|
|
699
678
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
700
679
|
process.exit(1);
|
|
701
680
|
});
|
package/dist/stdio.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/client.ts
|
|
4
4
|
var DEFAULT_BASE_URL = "https://www.cite42.dev/api/v1";
|
|
5
|
-
var
|
|
5
|
+
var FUNDS_LOCATION = "the Cite42 dashboard at https://www.cite42.dev/app";
|
|
6
6
|
function toUsd(micro) {
|
|
7
7
|
const [whole, fraction = ""] = (micro / 1e5).toFixed(5).split(".");
|
|
8
8
|
return `${whole}.${fraction.slice(0, Math.max(2, fraction.replace(/0+$/, "").length))}`;
|
|
@@ -19,16 +19,6 @@ function toCite42Balance(micro) {
|
|
|
19
19
|
function isJsonObject(value) {
|
|
20
20
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
21
|
}
|
|
22
|
-
function toCredits(microCredits) {
|
|
23
|
-
return microCredits / MICRO_CREDITS_PER_CREDIT;
|
|
24
|
-
}
|
|
25
|
-
function formatCredits(microCredits) {
|
|
26
|
-
const credits = toCredits(microCredits);
|
|
27
|
-
return credits.toLocaleString(
|
|
28
|
-
"en-US",
|
|
29
|
-
Number.isInteger(credits) ? { maximumFractionDigits: 0 } : { minimumFractionDigits: 0, maximumFractionDigits: 2 }
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
22
|
function isTrackerCommitment(billing) {
|
|
33
23
|
return typeof billing.balanceMicro === "number" && typeof billing.costMicroPerRun === "number";
|
|
34
24
|
}
|
|
@@ -52,38 +42,17 @@ function toCite42McpResult(value) {
|
|
|
52
42
|
...replay ? { originalChargedUsd: toUsd(originalCost) } : {},
|
|
53
43
|
message: (replay ? `Replay of an identical request from the last 2 minutes. This attempt was free; $${toUsd(originalCost)} USD was charged once, by the original Cite42 call.` : `Cite42: $${toUsd(cost)} USD charged; $${toUsd(balance)} USD remaining.`) + lowBalanceNotice
|
|
54
44
|
};
|
|
55
|
-
|
|
56
|
-
const message = `Cite42 credits: ${formatCredits(cost)} charged; ${formatCredits(balance)} remaining.`;
|
|
57
|
-
return {
|
|
58
|
-
...result,
|
|
59
|
-
cite42Billing,
|
|
60
|
-
// Legacy numeric fields retain their original units for existing integrations.
|
|
61
|
-
cite42Credits: {
|
|
62
|
-
charged: toCredits(cost),
|
|
63
|
-
remaining: toCredits(balance),
|
|
64
|
-
...replay ? { originalCharged: toCredits(originalCost) } : {},
|
|
65
|
-
message: replay ? `Replay of an identical request from the last 2 minutes. This attempt was free; ${toCredits(originalCost)} credits were charged once, by the original call.` : lowCredits ? `${message} Low Cite42 credit balance.` : message
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function toCite42CreditsBalance(balanceMicroCredits) {
|
|
70
|
-
return {
|
|
71
|
-
...toCite42Balance(balanceMicroCredits),
|
|
72
|
-
cite42Credits: {
|
|
73
|
-
remaining: toCredits(balanceMicroCredits),
|
|
74
|
-
message: `Cite42 credits: ${formatCredits(balanceMicroCredits)} remaining. This balance check was free.`
|
|
75
|
-
}
|
|
76
|
-
};
|
|
45
|
+
return { ...result, cite42Billing };
|
|
77
46
|
}
|
|
78
47
|
function errorMessageForMcp(status, path, body) {
|
|
79
48
|
try {
|
|
80
49
|
const payload = JSON.parse(body);
|
|
81
50
|
if (isJsonObject(payload) && isJsonObject(payload.error) && typeof payload.error.code === "string") {
|
|
82
51
|
if (payload.error.code === "daily_limit_exceeded" || payload.error.code === "monthly_limit_exceeded") {
|
|
83
|
-
return
|
|
52
|
+
return `Cite42 spending limit reached. Change it in ${FUNDS_LOCATION}.`;
|
|
84
53
|
}
|
|
85
54
|
if (payload.error.code === "insufficient_credits") {
|
|
86
|
-
return
|
|
55
|
+
return `Insufficient Cite42 balance. Add funds in ${FUNDS_LOCATION}.`;
|
|
87
56
|
}
|
|
88
57
|
}
|
|
89
58
|
} catch {
|
|
@@ -175,18 +144,18 @@ var modelEnum = z.enum(["chatgpt", "claude", "perplexity", "gemini", "google_ai_
|
|
|
175
144
|
var QUERY_MAX_CHARS = 2e3;
|
|
176
145
|
var SHORT_TEXT_MAX_CHARS = 200;
|
|
177
146
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
178
|
-
var MODEL_SELECTION_DESCRIPTION =
|
|
179
|
-
var FULL_SWEEP_DESCRIPTION = "Query all five AI surfaces
|
|
180
|
-
var
|
|
181
|
-
var
|
|
182
|
-
var
|
|
183
|
-
var KEYWORDS_PRICING = "
|
|
184
|
-
var WORKFLOW_PRICING = "
|
|
147
|
+
var MODEL_SELECTION_DESCRIPTION = "AI surfaces to query, each billed separately; pass a subset when the user names surfaces or wants a lower cost. Omit for the defaults (chatgpt, perplexity, gemini); fullSweep queries all five. google_ai_overview accepts queries up to 700 characters, the others 2000.";
|
|
148
|
+
var FULL_SWEEP_DESCRIPTION = "Query all five AI surfaces (adds claude and google_ai_overview) for full cross-surface coverage, at a higher cost. Ignored when models is given.";
|
|
149
|
+
var PAID_TOOL_BILLING = "Billed in USD (result field cite42Billing) to the user's prepaid Cite42 balance; exact prices from cite42_pricing (free). For plans over 20 calls or $5, estimate the cost and confirm with the user first.";
|
|
150
|
+
var RECOMMENDATIONS_INPUT_NOTE = "Its requestId can be passed to cite42_recommendations when the user asks for improvement advice.";
|
|
151
|
+
var MODEL_PRICING = "Priced per AI surface queried.";
|
|
152
|
+
var KEYWORDS_PRICING = "Billed by outcome: cheapest when the seed has no search volume, more when it has metrics, most with ideas:true.";
|
|
153
|
+
var WORKFLOW_PRICING = "Billed for the steps that succeed, at per-surface rates.";
|
|
185
154
|
function workflowDescription(description) {
|
|
186
|
-
return `${description} ${
|
|
155
|
+
return `${description} ${WORKFLOW_PRICING} ${PAID_TOOL_BILLING}`;
|
|
187
156
|
}
|
|
188
157
|
function readOnly(title, openWorldHint = true) {
|
|
189
|
-
return { annotations: { title, readOnlyHint: true, openWorldHint } };
|
|
158
|
+
return { annotations: { title, readOnlyHint: true, destructiveHint: false, openWorldHint } };
|
|
190
159
|
}
|
|
191
160
|
function mutation(title, options = {}) {
|
|
192
161
|
return {
|
|
@@ -208,7 +177,7 @@ function shortText(description) {
|
|
|
208
177
|
var aivRecommendations = createTool({
|
|
209
178
|
id: "cite42_recommendations",
|
|
210
179
|
mcp: readOnly("Recommend AI visibility next steps"),
|
|
211
|
-
description: "
|
|
180
|
+
description: "Page-level AI visibility recommendations, for when the user asks how to improve a page. With a saved result (requestId, trackerRunId or evidence) it reports on one measured prompt, charged only when the report includes page-comparison advice backed by quotes from inspected pages. Without one: url alone runs a free technical check of the page (robots.txt crawler access, indexing, rendering) and returns suggested buyer questions; url with query first runs a citations check, priced like cite42_citations, then the report. Prices are in cite42_pricing. Fetches the page, its robots.txt and up to two pages the AI surfaces cited, as returned HTML without JavaScript. No site-wide crawl, keyword advice or tracker activation. " + PAID_TOOL_BILLING,
|
|
212
181
|
inputSchema: z.object({
|
|
213
182
|
requestId: z.string().trim().min(1).max(128).optional().describe("A requestId from your own successful Cite42 call within the last 30 days."),
|
|
214
183
|
trackerRunId: z.string().trim().min(1).max(128).optional().describe("A specific finished run of your own tracker within the last 30 days."),
|
|
@@ -218,6 +187,9 @@ var aivRecommendations = createTool({
|
|
|
218
187
|
result: z.record(z.string(), z.unknown()),
|
|
219
188
|
measuredAt: z.string().datetime()
|
|
220
189
|
})).min(1).max(20).optional(),
|
|
190
|
+
url: z.string().trim().url().max(2e3).optional().describe("Your page that should answer the prompt. Without a saved result, url alone runs a free technical check; url with query runs a citations check first."),
|
|
191
|
+
query: z.string().trim().min(1).max(2e3).optional().describe("A buyer question. With a saved result it picks one measured prompt; without one it is the question measured."),
|
|
192
|
+
models: z.array(modelEnum).nonempty().optional().describe("AI surfaces for the citations check that runs without a saved result. Ignored with a saved result."),
|
|
221
193
|
context: z.object({
|
|
222
194
|
brand: z.string().trim().min(1).max(200).optional(),
|
|
223
195
|
domain: z.string().trim().min(1).max(250).optional(),
|
|
@@ -225,8 +197,13 @@ var aivRecommendations = createTool({
|
|
|
225
197
|
goal: z.string().trim().min(1).max(400).optional()
|
|
226
198
|
}).optional()
|
|
227
199
|
}).superRefine((input, ctx) => {
|
|
228
|
-
|
|
229
|
-
|
|
200
|
+
const sources = [input.requestId, input.trackerRunId, input.evidence].filter((value) => value !== void 0).length;
|
|
201
|
+
if (sources > 1) ctx.addIssue({ code: "custom", message: "Provide at most one of requestId, trackerRunId or evidence." });
|
|
202
|
+
if (sources === 0 && !input.url) {
|
|
203
|
+
ctx.addIssue({ code: "custom", message: "Provide requestId, trackerRunId or evidence, or url for a page without saved results." });
|
|
204
|
+
}
|
|
205
|
+
if (sources === 0 && input.query && (input.url?.length ?? 0) > 500) {
|
|
206
|
+
ctx.addIssue({ code: "custom", message: "url must be at most 500 characters to run a citations check." });
|
|
230
207
|
}
|
|
231
208
|
if (new TextEncoder().encode(JSON.stringify(input)).length > 64 * 1024) {
|
|
232
209
|
ctx.addIssue({ code: "custom", message: "Recommendation input must fit within 64KB." });
|
|
@@ -240,60 +217,52 @@ function optionalShortText(description) {
|
|
|
240
217
|
var modelsInput = z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
241
218
|
var fullSweepInput = z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
|
|
242
219
|
var competitorList = z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
220
|
+
async function fetchBalance() {
|
|
221
|
+
const usage = await aivGet("/usage");
|
|
222
|
+
const micro = usage.balance?.microCredits;
|
|
223
|
+
if (typeof micro !== "number" || !Number.isFinite(micro)) {
|
|
224
|
+
throw new Error("Cite42 usage response did not include a balance.");
|
|
225
|
+
}
|
|
226
|
+
return toCite42Balance(micro);
|
|
227
|
+
}
|
|
243
228
|
var aivBalance = createTool({
|
|
244
229
|
id: "cite42_balance",
|
|
245
230
|
mcp: readOnly("Check Cite42 balance", false),
|
|
246
|
-
description: "Check the remaining Cite42 balance for the connected account. Free.
|
|
231
|
+
description: "Check the remaining Cite42 balance in USD for the connected account. Free.",
|
|
247
232
|
inputSchema: z.object({}),
|
|
248
|
-
execute:
|
|
249
|
-
const usage = await aivGet("/usage");
|
|
250
|
-
const microCredits = usage.balance?.microCredits;
|
|
251
|
-
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
252
|
-
throw new Error("Cite42 usage response did not include a balance.");
|
|
253
|
-
}
|
|
254
|
-
return toCite42Balance(microCredits);
|
|
255
|
-
}
|
|
233
|
+
execute: fetchBalance
|
|
256
234
|
});
|
|
257
235
|
var aivCredits = createTool({
|
|
258
236
|
id: "cite42_credits",
|
|
259
237
|
mcp: readOnly("Check Cite42 balance (legacy alias)", false),
|
|
260
|
-
description: "
|
|
238
|
+
description: "Alias of cite42_balance for older integrations. Free.",
|
|
261
239
|
inputSchema: z.object({}),
|
|
262
|
-
execute:
|
|
263
|
-
const usage = await aivGet("/usage");
|
|
264
|
-
const microCredits = usage.balance?.microCredits;
|
|
265
|
-
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
266
|
-
throw new Error("Cite42 usage response did not include a balance.");
|
|
267
|
-
}
|
|
268
|
-
return toCite42CreditsBalance(microCredits);
|
|
269
|
-
}
|
|
240
|
+
execute: fetchBalance
|
|
270
241
|
});
|
|
271
242
|
var aivPricing = createTool({
|
|
272
243
|
id: "cite42_pricing",
|
|
273
244
|
mcp: readOnly("Get Cite42 price table", false),
|
|
274
|
-
description:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
execute: async (input) => input.currency === "USD" ? aivGet("/pricing?currency=USD") : { ...await aivGet("/pricing"), usd: await aivGet("/pricing?currency=USD") }
|
|
245
|
+
description: "Get the exact Cite42 price table in USD: per-surface rates, flat-rate tools, workflow maximums, and which tools are free. Use it for cost questions and to estimate a plan before running it. Free.",
|
|
246
|
+
// No currency switch: prices are USD only. An older client's `currency` argument is ignored.
|
|
247
|
+
inputSchema: z.object({}),
|
|
248
|
+
execute: async () => aivGet("/pricing?currency=USD")
|
|
279
249
|
});
|
|
280
250
|
var aivUsage = createTool({
|
|
281
251
|
id: "cite42_usage",
|
|
282
252
|
mcp: readOnly("Get recent Cite42 usage", false),
|
|
283
|
-
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining balance in USD. Free.
|
|
253
|
+
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining balance in USD. Free.",
|
|
284
254
|
inputSchema: z.object({}),
|
|
285
255
|
execute: async () => {
|
|
286
256
|
const usage = await aivGet("/usage");
|
|
287
|
-
const
|
|
288
|
-
if (typeof
|
|
257
|
+
const micro = usage.balance?.microCredits;
|
|
258
|
+
if (typeof micro !== "number" || !Number.isFinite(micro)) {
|
|
289
259
|
throw new Error("Cite42 usage response did not include a balance.");
|
|
290
260
|
}
|
|
291
261
|
return {
|
|
292
|
-
...
|
|
262
|
+
...toCite42Balance(micro),
|
|
293
263
|
recentCalls: (usage.recent ?? []).slice(0, 20).map((row) => ({
|
|
294
264
|
endpoint: row.endpoint,
|
|
295
265
|
status: row.status,
|
|
296
|
-
credits: typeof row.costMicro === "number" ? toCredits(row.costMicro) : void 0,
|
|
297
266
|
costUsd: typeof row.costMicro === "number" ? toUsd(row.costMicro) : void 0,
|
|
298
267
|
at: row.createdAt
|
|
299
268
|
}))
|
|
@@ -303,7 +272,7 @@ var aivUsage = createTool({
|
|
|
303
272
|
var aivSearch = createTool({
|
|
304
273
|
id: "cite42_search",
|
|
305
274
|
mcp: readOnly("Search AI models"),
|
|
306
|
-
description: `Run a query against AI models (ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews) and return each model's answer along with the sources it cited. ${MODEL_PRICING} ${
|
|
275
|
+
description: `Run a query against AI models (ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews) and return each model's answer along with the sources it cited. ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
307
276
|
inputSchema: z.object({
|
|
308
277
|
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
309
278
|
models: modelsInput,
|
|
@@ -314,7 +283,7 @@ var aivSearch = createTool({
|
|
|
314
283
|
var aivCitations = createTool({
|
|
315
284
|
id: "cite42_citations",
|
|
316
285
|
mcp: readOnly("Find AI citations"),
|
|
317
|
-
description: `Find which sources a model's web search surfaces for a query. Aggregates cited URLs across models with per-model counts; optionally checks whether a specific URL is cited. Measures the retrieval corpus behind a prompt \u2014 which domains own your category in AI search, and where your content is missing \u2014 not the citation links a consumer chat UI renders for a logged-in user. ${MODEL_PRICING} ${
|
|
286
|
+
description: `Find which sources a model's web search surfaces for a query. Aggregates cited URLs across models with per-model counts; optionally checks whether a specific URL is cited. Measures the retrieval corpus behind a prompt \u2014 which domains own your category in AI search, and where your content is missing \u2014 not the citation links a consumer chat UI renders for a logged-in user. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
318
287
|
inputSchema: z.object({
|
|
319
288
|
query: queryText("The concise query to look up cited sources for."),
|
|
320
289
|
url: z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
|
|
@@ -326,7 +295,7 @@ var aivCitations = createTool({
|
|
|
326
295
|
var aivRankings = createTool({
|
|
327
296
|
id: "cite42_rankings",
|
|
328
297
|
mcp: readOnly("Measure AI brand rankings"),
|
|
329
|
-
description: `Measure how a set of brands/competitors rank in AI answers for a query: mention rate, average position, and a per-model breakdown across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews. ${MODEL_PRICING} ${
|
|
298
|
+
description: `Measure how a set of brands/competitors rank in AI answers for a query: mention rate, average position, and a per-model breakdown across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
330
299
|
inputSchema: z.object({
|
|
331
300
|
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
332
301
|
competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
|
|
@@ -338,7 +307,7 @@ var aivRankings = createTool({
|
|
|
338
307
|
var aivCompare = createTool({
|
|
339
308
|
id: "cite42_compare",
|
|
340
309
|
mcp: readOnly("Compare brand against competitors"),
|
|
341
|
-
description: `Compare one primary brand head-to-head against a competitor set in AI answers: per-brand rankings plus a co-mention graph of which brands the models name together. ${MODEL_PRICING} ${
|
|
310
|
+
description: `Compare one primary brand head-to-head against a competitor set in AI answers: per-brand rankings plus a co-mention graph of which brands the models name together. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
342
311
|
inputSchema: z.object({
|
|
343
312
|
query: queryText("The concise query to compare the brand and competitors on."),
|
|
344
313
|
brand: shortText("Your brand, ranked alongside the competitors."),
|
|
@@ -351,7 +320,7 @@ var aivCompare = createTool({
|
|
|
351
320
|
var aivSentiment = createTool({
|
|
352
321
|
id: "cite42_sentiment",
|
|
353
322
|
mcp: readOnly("Score brand sentiment in AI answers"),
|
|
354
|
-
description: `Score how each AI model talks about a brand for a query: a positive/neutral/negative split per model with the verbatim phrases driving the tone, plus an aggregate across models. ${MODEL_PRICING} ${
|
|
323
|
+
description: `Score how each AI model talks about a brand for a query: a positive/neutral/negative split per model with the verbatim phrases driving the tone, plus an aggregate across models. ${RECOMMENDATIONS_INPUT_NOTE} ${MODEL_PRICING} ${PAID_TOOL_BILLING}`,
|
|
355
324
|
inputSchema: z.object({
|
|
356
325
|
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
357
326
|
brand: shortText("The brand to score sentiment for in each model answer."),
|
|
@@ -363,7 +332,7 @@ var aivSentiment = createTool({
|
|
|
363
332
|
var aivKeywords = createTool({
|
|
364
333
|
id: "cite42_keywords",
|
|
365
334
|
mcp: readOnly("Get SEO keyword data"),
|
|
366
|
-
description: `Classic keyword data for a seed term. Omit ideas when the user only needs that seed's monthly search volume, CPC, or competition. Set ideas:true only when the user asks for related, alternative, or long-tail suggestions from provider candidates. Not a multi-model AI-search query. The reply echoes requestedKeyword and carries status "exact" or "no_data"; when the seed has no search volume, any related term that does appears under alternatives \u2014 never treat an alternative's metrics as the requested keyword's. ${KEYWORDS_PRICING} ${
|
|
335
|
+
description: `Classic keyword data for a seed term. Omit ideas when the user only needs that seed's monthly search volume, CPC, or competition. Set ideas:true only when the user asks for related, alternative, or long-tail suggestions from provider candidates. Not a multi-model AI-search query. The reply echoes requestedKeyword and carries status "exact" or "no_data"; when the seed has no search volume, any related term that does appears under alternatives \u2014 never treat an alternative's metrics as the requested keyword's. ${KEYWORDS_PRICING} ${PAID_TOOL_BILLING}`,
|
|
367
336
|
inputSchema: z.object({
|
|
368
337
|
seed: queryText("Seed keyword or concise topic to fetch search volume, CPC, and competition for."),
|
|
369
338
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
@@ -379,7 +348,7 @@ var aivKeywords = createTool({
|
|
|
379
348
|
var aivKeywordsBatch = createTool({
|
|
380
349
|
id: "cite42_keywords_batch",
|
|
381
350
|
mcp: readOnly("Get batch SEO keyword data"),
|
|
382
|
-
description: `Check search volume, CPC, and competition for two to 50 distinct seed keywords in one request. No related ideas. Billed by outcome
|
|
351
|
+
description: `Check search volume, CPC, and competition for two to 50 distinct seed keywords in one request. No related ideas. Billed by outcome. ${PAID_TOOL_BILLING}`,
|
|
383
352
|
inputSchema: z.object({
|
|
384
353
|
seeds: z.array(z.string().trim().min(1).max(80).refine(
|
|
385
354
|
(seed) => seed.split(/\s+/).length <= 10,
|
|
@@ -396,7 +365,7 @@ var aivKeywordsBatch = createTool({
|
|
|
396
365
|
var aivTrends = createTool({
|
|
397
366
|
id: "cite42_trends",
|
|
398
367
|
mcp: readOnly("Get Google Trends data"),
|
|
399
|
-
description: `Google Trends interest-over-time, related and rising queries, and a rising/falling/stable label for a term. A flat-priced data tool, not an AI-model query. ${
|
|
368
|
+
description: `Google Trends interest-over-time, related and rising queries, and a rising/falling/stable label for a term. A flat-priced data tool, not an AI-model query. ${PAID_TOOL_BILLING}`,
|
|
400
369
|
inputSchema: z.object({
|
|
401
370
|
term: queryText("Search term or concise topic to pull Google Trends interest-over-time for."),
|
|
402
371
|
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
|
|
@@ -407,7 +376,7 @@ var aivTrends = createTool({
|
|
|
407
376
|
var aivRedditTrends = createTool({
|
|
408
377
|
id: "cite42_reddit_trends",
|
|
409
378
|
mcp: readOnly("Find Reddit discussions"),
|
|
410
|
-
description:
|
|
379
|
+
description: "Discover relevant Reddit discussions with Google Search and read up to three thread pages through Gemini URL context. Returns Gemini-read titles, authors, post excerpts, relative ages, available scores/comment counts and sampled human comments, plus cited model summaries. Check coverage and warnings: pages may be cached, extraction is not independently verified, and exact dates, growth and quote-based insights are unavailable. risingThreads is a deprecated alias of relevantThreads. " + PAID_TOOL_BILLING,
|
|
411
380
|
inputSchema: z.object({
|
|
412
381
|
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
413
382
|
subreddits: z.array(shortText("Subreddit name without r/.")).max(20).optional().describe("Optional subreddit names, without r/."),
|
|
@@ -418,7 +387,7 @@ var aivRedditTrends = createTool({
|
|
|
418
387
|
var aivYoutubeTrends = createTool({
|
|
419
388
|
id: "cite42_youtube_trends",
|
|
420
389
|
mcp: readOnly("Get YouTube trend data"),
|
|
421
|
-
description: `YouTube trend data for rising videos, creator angles, title patterns, channel examples, and video-market opportunities around a topic. ${
|
|
390
|
+
description: `YouTube trend data for rising videos, creator angles, title patterns, channel examples, and video-market opportunities around a topic. ${PAID_TOOL_BILLING}`,
|
|
422
391
|
inputSchema: z.object({
|
|
423
392
|
topic: queryText("Topic, keyword, niche, or audience to research on YouTube."),
|
|
424
393
|
region: z.string().trim().max(8).optional().describe('ISO-2 country/region code. Defaults to "US".'),
|
|
@@ -439,7 +408,9 @@ var aivFindContentOpportunities = createTool({
|
|
|
439
408
|
brand: optionalShortText("Optional brand/domain to account for."),
|
|
440
409
|
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
441
410
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
442
|
-
channels: z.array(channelEnum).optional().describe("Optional data sources to include.")
|
|
411
|
+
channels: z.array(channelEnum).optional().describe("Optional data sources to include."),
|
|
412
|
+
models: modelsInput,
|
|
413
|
+
fullSweep: fullSweepInput
|
|
443
414
|
}),
|
|
444
415
|
execute: async (input, context) => aivPost("/workflows/content-opportunities", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
445
416
|
});
|
|
@@ -452,7 +423,9 @@ var aivAnalyzeTopicDemand = createTool({
|
|
|
452
423
|
inputSchema: z.object({
|
|
453
424
|
topic: queryText("Topic, keyword, category, or audience demand to analyze."),
|
|
454
425
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
455
|
-
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
426
|
+
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".'),
|
|
427
|
+
models: modelsInput,
|
|
428
|
+
fullSweep: fullSweepInput
|
|
456
429
|
}),
|
|
457
430
|
execute: async (input, context) => aivPost("/workflows/topic-demand", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
458
431
|
});
|
|
@@ -460,7 +433,7 @@ var aivMapAiPrompts = createTool({
|
|
|
460
433
|
id: "cite42_map_ai_prompts",
|
|
461
434
|
mcp: readOnly("Map buyer prompts for AI search"),
|
|
462
435
|
description: workflowDescription(
|
|
463
|
-
"Run the AI-prompt-map workflow: research for a list of buyer prompts worth tracking, from AI search, SEO demand, trends, citations, Reddit, and YouTube data. It returns evidence and a suggested prompt, not a finished list
|
|
436
|
+
"Run the AI-prompt-map workflow: research for a list of buyer prompts worth tracking, from AI search, SEO demand, trends, citations, Reddit, and YouTube data. It returns evidence and a suggested prompt, not a finished list; prompts written from it can be tracked with cite42_tracker_create."
|
|
464
437
|
),
|
|
465
438
|
inputSchema: z.object({
|
|
466
439
|
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
@@ -468,7 +441,9 @@ var aivMapAiPrompts = createTool({
|
|
|
468
441
|
brand: optionalShortText("Optional brand/domain to include in prompt tracking recommendations."),
|
|
469
442
|
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
470
443
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
471
|
-
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
444
|
+
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".'),
|
|
445
|
+
models: modelsInput,
|
|
446
|
+
fullSweep: fullSweepInput
|
|
472
447
|
}),
|
|
473
448
|
execute: async (input, context) => aivPost("/workflows/ai-prompt-map", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
474
449
|
});
|
|
@@ -482,7 +457,9 @@ var aivAnalyzeCompetitorContent = createTool({
|
|
|
482
457
|
competitors: competitorList.describe("Competitor brands, domains, or channels to analyze."),
|
|
483
458
|
topic: z.string().trim().min(1).max(QUERY_MAX_CHARS).optional().describe("Optional topic/category to constrain the analysis."),
|
|
484
459
|
brand: optionalShortText("Optional primary brand/domain to compare against."),
|
|
485
|
-
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
460
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
461
|
+
models: modelsInput,
|
|
462
|
+
fullSweep: fullSweepInput
|
|
486
463
|
}),
|
|
487
464
|
execute: async (input, context) => aivPost("/workflows/competitor-content", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
488
465
|
});
|
|
@@ -513,7 +490,9 @@ var aivGenerateContentBrief = createTool({
|
|
|
513
490
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
514
491
|
brand: optionalShortText("Optional brand/domain the brief is for."),
|
|
515
492
|
goal: optionalShortText("Optional content goal, e.g. rank, earn citations, convert."),
|
|
516
|
-
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
493
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
494
|
+
models: modelsInput,
|
|
495
|
+
fullSweep: fullSweepInput
|
|
517
496
|
}),
|
|
518
497
|
execute: async (input, context) => aivPost("/workflows/content-brief", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
519
498
|
});
|
|
@@ -569,11 +548,11 @@ var trackerDefinitionFields = {
|
|
|
569
548
|
"AI surfaces to query for every prompt. Omit for the three defaults (chatgpt, perplexity, gemini), or list surfaces explicitly \u2014 including all five \u2014 to fix the recurring cost."
|
|
570
549
|
)
|
|
571
550
|
};
|
|
572
|
-
var TRACKER_BILLING_DESCRIPTION = "
|
|
551
|
+
var TRACKER_BILLING_DESCRIPTION = "Managing trackers and reading reports is free; each run bills one call per prompt at the per-surface rates.";
|
|
573
552
|
var aivTrackerCreate = createTool({
|
|
574
553
|
id: "cite42_tracker_create",
|
|
575
554
|
mcp: mutation("Create Cite42 tracker draft"),
|
|
576
|
-
description: "Create a draft weekly or monthly tracker: one measurement \u2014 rankings, compare, citations, or sentiment \u2014 applied to a list of prompts, with the brand, competitors and AI surfaces set once for all of them.
|
|
555
|
+
description: "Create a draft weekly or monthly tracker: one measurement \u2014 rankings, compare, citations, or sentiment \u2014 applied to a list of prompts, with the brand, competitors and AI surfaces set once for all of them. Prompt lists can come from cite42_map_ai_prompts research. Creation does not start recurring calls. " + TRACKER_BILLING_DESCRIPTION,
|
|
577
556
|
inputSchema: z.object(trackerDefinitionFields),
|
|
578
557
|
execute: async (input) => aivPost("/trackers", input)
|
|
579
558
|
});
|
|
@@ -590,7 +569,7 @@ var aivTrackerActivate = createTool({
|
|
|
590
569
|
openWorldHint: true,
|
|
591
570
|
idempotentHint: true
|
|
592
571
|
}),
|
|
593
|
-
description: `Activate or resume recurring weekly or monthly tool calls. maxCostMicroPerRun must equal the tracker
|
|
572
|
+
description: `Activate or resume recurring weekly or monthly tool calls. maxCostMicroPerRun must equal the tracker's latest estimatedCostMicroPerRun; activation authorizes future billed calls until paused. The response includes the balance, the cost per run, and runsDryAt, the date the balance stops covering active trackers. When tracker emails are enabled, activation sends a setup summary and every completed run sends current results and suggested next steps, even when nothing changed. Activation is refused when the balance cannot cover the next run. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
594
573
|
inputSchema: z.object({
|
|
595
574
|
trackerId: trackerIdInput,
|
|
596
575
|
maxCostMicroPerRun: z.number().int().nonnegative().describe("Exact estimatedCostMicroPerRun returned by the latest tracker record.")
|
|
@@ -694,7 +673,7 @@ if (!resolveApiKey()) {
|
|
|
694
673
|
);
|
|
695
674
|
process.exit(1);
|
|
696
675
|
}
|
|
697
|
-
buildCite42Server("0.
|
|
676
|
+
buildCite42Server("0.8.0").startStdio().catch((error) => {
|
|
698
677
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
699
678
|
process.exit(1);
|
|
700
679
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cite42/mcp",
|
|
3
3
|
"mcpName": "dev.cite42/mcp",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"description": "Cite42 MCP server: AI visibility and SEO data for AI agents. See how the AI models behind ChatGPT, Claude, Perplexity and Gemini, plus Google AI Overviews, mention your brand and which sources they cite, with SEO keyword data, Google, Reddit and YouTube trends, and weekly or monthly trackers that watch a list of prompts over time. Works with Claude Code, Claude Desktop, Codex CLI, Cursor, and any MCP client.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|