@cite42/mcp 0.5.1 → 0.6.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 +67 -10
- package/dist/stdio.cjs +125 -30
- package/dist/stdio.mjs +125 -30
- package/package.json +13 -7
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Once connected, Claude Code, Claude Desktop, Codex CLI, or Cursor can:
|
|
|
10
10
|
- **Track brand visibility** in AI answers: rankings, head-to-head comparisons, and sentiment
|
|
11
11
|
- **Schedule weekly or monthly trackers**: one measurement — rankings, comparisons, citations, or sentiment — over a list of prompts, with setup confirmation and a useful email after every run, then ask your agent for stored history
|
|
12
12
|
- **Pull SEO data**: keyword search volume, CPC, competition, and Google Trends
|
|
13
|
-
- **
|
|
13
|
+
- **Research Reddit discussions and YouTube content** for audience questions, pain points, and content ideas
|
|
14
14
|
- **Run full research workflows**: content gaps, topic demand, AI prompt maps, competitor analysis, and content briefs in a single call
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -18,7 +18,7 @@ Once connected, Claude Code, Claude Desktop, Codex CLI, or Cursor can:
|
|
|
18
18
|
## Requirements
|
|
19
19
|
|
|
20
20
|
- **A Cite42 account**, free to create at [cite42.dev](https://www.cite42.dev)
|
|
21
|
-
- **Node.js
|
|
21
|
+
- **Node.js 22.13 or newer and a Cite42 API key** for Claude Code, Codex CLI, and Cursor
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
@@ -94,7 +94,7 @@ You do not need to keep `npx -y @cite42/mcp` running in a separate terminal. Loc
|
|
|
94
94
|
- **Claude Code:** run `claude mcp list` and check that `cite42` shows as ✔ connected. Or type `/mcp` inside a session.
|
|
95
95
|
- **Claude Desktop:** open **Settings → Connectors** and confirm Cite42 is connected.
|
|
96
96
|
- **Codex CLI:** run `codex mcp list`.
|
|
97
|
-
- **Cursor:** look for `cite42` in the MCP/tools indicator.
|
|
97
|
+
- **Cursor:** look for `cite42` in the MCP/tools indicator. 29 tools should be listed.
|
|
98
98
|
|
|
99
99
|
If it doesn't appear, see [Troubleshooting](#troubleshooting).
|
|
100
100
|
|
|
@@ -107,7 +107,7 @@ After connecting or restarting, just ask in plain language. Your agent picks the
|
|
|
107
107
|
- *"Get search volume, CPC, and related keywords for 'ai seo tools'."*
|
|
108
108
|
- *"What are people on Reddit complaining about regarding email marketing tools?"*
|
|
109
109
|
- *"Run a content brief for the topic 'ai website builder'."*
|
|
110
|
-
- *"Map the prompts worth tracking for our category, then create a weekly Cite42 rankings tracker for the best five. Show me the estimated
|
|
110
|
+
- *"Map the prompts worth tracking for our category, then create a weekly Cite42 rankings tracker for the best five. Show me the estimated cost in USD per run before activating it."*
|
|
111
111
|
|
|
112
112
|
### Example: what a session looks like
|
|
113
113
|
|
|
@@ -130,11 +130,12 @@ That is the three default surfaces; ask for `fullSweep` to add Claude and Google
|
|
|
130
130
|
|
|
131
131
|
## Tools
|
|
132
132
|
|
|
133
|
-
Cite42 exposes
|
|
133
|
+
Cite42 exposes 29 MCP tools: 4 free account tools (including the compatibility alias), 11 data/report tools, 6 research workflows, and 8 tracker tools.
|
|
134
134
|
|
|
135
135
|
### Account & pricing (free)
|
|
136
136
|
|
|
137
|
-
- `
|
|
137
|
+
- `cite42_balance`: check the connected account’s remaining Cite42 balance in USD.
|
|
138
|
+
- `cite42_credits`: compatibility alias for older integrations.
|
|
138
139
|
- `cite42_pricing`: get current per-tool prices and estimate a planned set of calls.
|
|
139
140
|
- `cite42_usage`: read recent call status and cost together with the remaining balance.
|
|
140
141
|
|
|
@@ -145,12 +146,14 @@ Cite42 exposes 26 MCP tools: 3 free account tools, 9 data tools, 6 research work
|
|
|
145
146
|
- `cite42_rankings`: measure how brands rank in AI answers, including mention rate, average position, and per-model breakdown.
|
|
146
147
|
- `cite42_compare`: compare one brand against competitors in AI answers.
|
|
147
148
|
- `cite42_sentiment`: score positive, neutral, and negative sentiment for a brand, with supporting phrases.
|
|
149
|
+
- `cite42_recommendations`: request an optional paid next-step report using saved results or supplied evidence, with business context. Insufficient evidence and rule-only fallback are free.
|
|
148
150
|
|
|
149
151
|
### SEO & trend data
|
|
150
152
|
|
|
151
153
|
- `cite42_keywords`: get seed search volume, CPC, and competition, with optional related keyword ideas.
|
|
154
|
+
- `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).
|
|
152
155
|
- `cite42_trends`: get Google Trends interest over time, related and rising queries, and a trend label.
|
|
153
|
-
- `cite42_reddit_trends`: find Reddit
|
|
156
|
+
- `cite42_reddit_trends`: find relevant Reddit discussions and supported questions, pain points, and product mentions, with source quotes. Uses a limited sample of titles and excerpts, not complete comment conversations or measured growth. Read `relevantThreads`; `risingThreads` remains a deprecated compatibility alias. Check `analysis.status` and `warnings` for coverage and analysis failures.
|
|
154
157
|
- `cite42_youtube_trends`: find YouTube rising videos, creator angles, title patterns, and opportunities.
|
|
155
158
|
|
|
156
159
|
### Research workflows (multi-source, one call)
|
|
@@ -186,14 +189,18 @@ npx -y @cite42/mcp --api-key=cite42_live_your_key_here --api-base=https://www.ci
|
|
|
186
189
|
|
|
187
190
|
## Billing
|
|
188
191
|
|
|
189
|
-
Tool and workflow calls
|
|
192
|
+
Tool and workflow calls charge your Cite42 balance in USD. Tracker management and stored-history reads are free; every scheduled or manual tracker run bills one call per saved prompt at the normal rate. Activating or resuming a tracker requires explicit confirmation of the current maximum cost in USD per run and authorizes recurring billed calls until you pause it. Calls fail with a clear error if the API key is missing or your balance runs out. See [www.cite42.dev/pricing](https://www.cite42.dev/pricing).
|
|
190
193
|
|
|
191
194
|
## Troubleshooting
|
|
192
195
|
|
|
196
|
+
For contributor testing, run `npm run inspect` to open the official MCP Inspector with local
|
|
197
|
+
stdio, published npm, and local OAuth targets. Run `npm run test:stdio` for a free built-executable
|
|
198
|
+
smoke test against a loopback fake API. See [local MCP testing](../../docs/mcp-testing.md) in the repository.
|
|
199
|
+
|
|
193
200
|
- **Server not listed after setup**: make sure you fully restarted the client (new terminal for CLI clients, full quit for desktop apps).
|
|
194
|
-
- **`command not found: npx`**: install Node.js
|
|
201
|
+
- **`command not found: npx`**: install Node.js 22.13+ from [nodejs.org](https://nodejs.org), then restart your terminal.
|
|
195
202
|
- **Tool calls fail with an auth error**: check the key was passed correctly (`CITE42_API_KEY`, no quotes or trailing spaces) and is active at [www.cite42.dev/app/keys](https://www.cite42.dev/app/keys).
|
|
196
|
-
- **Tool calls fail with
|
|
203
|
+
- **Tool calls fail with an insufficient-balance error**: add funds at [www.cite42.dev/app](https://www.cite42.dev/app).
|
|
197
204
|
|
|
198
205
|
## Links
|
|
199
206
|
|
|
@@ -201,3 +208,53 @@ Tool and workflow calls consume Cite42 credits from your account. Tracker manage
|
|
|
201
208
|
- API keys: [Create a Cite42 API key](https://www.cite42.dev/app/keys)
|
|
202
209
|
- Docs: [Cite42 API & MCP documentation](https://www.cite42.dev/docs)
|
|
203
210
|
- Pricing: [Cite42 pay-per-call pricing](https://www.cite42.dev/pricing)
|
|
211
|
+
|
|
212
|
+
### USD billing and older clients
|
|
213
|
+
|
|
214
|
+
Quote the `cite42Billing` block for the amount charged and remaining Cite42 balance.
|
|
215
|
+
Its `chargedUsd` and `remainingUsd` values are decimal strings in USD. Legacy
|
|
216
|
+
`cite42Credits` numbers keep their original units for compatibility.
|
|
217
|
+
|
|
218
|
+
Call `cite42_pricing` with `{ "currency": "USD" }` for dollar prices. With no argument,
|
|
219
|
+
the legacy table is preserved and dollar prices are in the `usd` block.
|
|
220
|
+
REST clients can use `GET /v1/pricing?currency=USD`; the default REST response remains
|
|
221
|
+
compatible with existing installations. Update the MCP package to receive the new
|
|
222
|
+
USD guidance and `cite42_balance` tool. Tracker activation still accepts the exact
|
|
223
|
+
integer `maxCostMicroPerRun` returned by the API; present its USD equivalent to users.
|
|
224
|
+
|
|
225
|
+
## Optional AI visibility recommendations
|
|
226
|
+
|
|
227
|
+
`cite42_rankings`, `cite42_compare`, `cite42_citations` and `cite42_sentiment` include a `metrics`
|
|
228
|
+
block. Show mention/citation percentages with their counts (for example, **66.7% — 2 of 3 checked
|
|
229
|
+
answers**). Sentiment percentages describe AI-estimated tone, not the percentage of positive answers.
|
|
230
|
+
Failed, empty and incomplete observations are excluded; no usable sample is unavailable, not zero.
|
|
231
|
+
Only comparable tracker runs provide automatic history. Each reported run includes `metrics` grouped
|
|
232
|
+
by primitive and brand/watched URL, with `current`, `previous`, `previousRunId`, and `changes`.
|
|
233
|
+
Each group states `models` and `methodologyVersion`. Its `comparison` is `comparable`, `baseline`
|
|
234
|
+
(no earlier run, or a new measurement baseline after an old-format run), or `unavailable`;
|
|
235
|
+
`comparisonReason` explains skipped comparisons. A successful run with unavailable comparison data
|
|
236
|
+
is not a failed run, and its email does not claim "no changes". Completed manual-run replays include
|
|
237
|
+
the same immediate predecessor as tracker reports.
|
|
238
|
+
Citation groups preserve the original watched URL; normalized identities are only used for matching.
|
|
239
|
+
Counts sum distinct prompts: four prompts × five surfaces can supply 20 answers. Comparisons require
|
|
240
|
+
the complete prompt/configuration set, methodology and complete coverage to match the preceding run.
|
|
241
|
+
Individual prompts remain small samples (at most five surfaces); percentage-point changes are withheld
|
|
242
|
+
below 10 answers per group in each run. This guardrail does not establish statistical significance.
|
|
243
|
+
|
|
244
|
+
`cite42_recommendations` is a separate, optional paid report. Use exactly one `requestId` returned by
|
|
245
|
+
an earlier call, a user-owned `trackerRunId`, or a structured `evidence` array. Optional `context`
|
|
246
|
+
accepts brand, domain, audience and goal for this request only. Check `cite42_pricing` for the current
|
|
247
|
+
flat report price and obtain the user's agreement before running the add-on. Insufficient evidence
|
|
248
|
+
and rule-only fallback are free.
|
|
249
|
+
|
|
250
|
+
For rankings evidence, provide `context.brand` to identify the tracked brand you want advice for.
|
|
251
|
+
Without it, the measurements remain available but the tool asks which brand to focus on for free;
|
|
252
|
+
it never guesses ownership or advises you to improve a competitor's offering. Comparison evidence
|
|
253
|
+
already identifies its target through `input.brand`.
|
|
254
|
+
|
|
255
|
+
Sources must be complete and no more than 30 days old, within 20 observations and 64KB. Supported
|
|
256
|
+
successful workflow steps can be reused. The tool does not inspect websites, retrieve host memory,
|
|
257
|
+
perform fresh research, recommend keywords, or activate paid tracking. Suggestions are labelled
|
|
258
|
+
inferred and do not establish website defects or causes. Tracker follow-up requires a cadence and
|
|
259
|
+
timezone choice plus separate activation approval. Identical completed retries are free for two
|
|
260
|
+
minutes; an in-flight duplicate is refused without another charge and can be retried shortly.
|
package/dist/stdio.cjs
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
// src/client.ts
|
|
5
5
|
var DEFAULT_BASE_URL = "https://www.cite42.dev/api/v1";
|
|
6
6
|
var MICRO_CREDITS_PER_CREDIT = 1e3;
|
|
7
|
+
function toUsd(micro) {
|
|
8
|
+
const [whole, fraction = ""] = (micro / 1e5).toFixed(5).split(".");
|
|
9
|
+
return `${whole}.${fraction.slice(0, Math.max(2, fraction.replace(/0+$/, "").length))}`;
|
|
10
|
+
}
|
|
11
|
+
function toCite42Balance(micro) {
|
|
12
|
+
return {
|
|
13
|
+
cite42Billing: {
|
|
14
|
+
currency: "USD",
|
|
15
|
+
remainingUsd: toUsd(micro),
|
|
16
|
+
message: `Cite42 balance: $${toUsd(micro)} USD remaining. This balance check was free.`
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
7
20
|
function isJsonObject(value) {
|
|
8
21
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
22
|
}
|
|
@@ -30,19 +43,33 @@ function toCite42McpResult(value) {
|
|
|
30
43
|
if (typeof cost !== "number" || !Number.isFinite(cost) || typeof balance !== "number" || !Number.isFinite(balance)) {
|
|
31
44
|
return result;
|
|
32
45
|
}
|
|
46
|
+
const originalCost = isJsonObject(billing.originalCost) ? billing.originalCost.microCredits : void 0;
|
|
47
|
+
const replay = typeof result.replayOf === "string" && typeof originalCost === "number" && Number.isFinite(originalCost);
|
|
48
|
+
const lowBalanceNotice = typeof warning === "string" ? " Low Cite42 balance." : "";
|
|
49
|
+
const cite42Billing = {
|
|
50
|
+
currency: "USD",
|
|
51
|
+
chargedUsd: toUsd(cost),
|
|
52
|
+
remainingUsd: toUsd(balance),
|
|
53
|
+
...replay ? { originalChargedUsd: toUsd(originalCost) } : {},
|
|
54
|
+
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
|
+
};
|
|
33
56
|
const lowCredits = typeof warning === "string";
|
|
34
57
|
const message = `Cite42 credits: ${formatCredits(cost)} charged; ${formatCredits(balance)} remaining.`;
|
|
35
58
|
return {
|
|
36
59
|
...result,
|
|
60
|
+
cite42Billing,
|
|
61
|
+
// Legacy numeric fields retain their original units for existing integrations.
|
|
37
62
|
cite42Credits: {
|
|
38
63
|
charged: toCredits(cost),
|
|
39
64
|
remaining: toCredits(balance),
|
|
40
|
-
|
|
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
|
|
41
67
|
}
|
|
42
68
|
};
|
|
43
69
|
}
|
|
44
70
|
function toCite42CreditsBalance(balanceMicroCredits) {
|
|
45
71
|
return {
|
|
72
|
+
...toCite42Balance(balanceMicroCredits),
|
|
46
73
|
cite42Credits: {
|
|
47
74
|
remaining: toCredits(balanceMicroCredits),
|
|
48
75
|
message: `Cite42 credits: ${formatCredits(balanceMicroCredits)} remaining. This balance check was free.`
|
|
@@ -54,10 +81,10 @@ function errorMessageForMcp(status, path, body) {
|
|
|
54
81
|
const payload = JSON.parse(body);
|
|
55
82
|
if (isJsonObject(payload) && isJsonObject(payload.error) && typeof payload.error.code === "string") {
|
|
56
83
|
if (payload.error.code === "daily_limit_exceeded" || payload.error.code === "monthly_limit_exceeded") {
|
|
57
|
-
return "Cite42
|
|
84
|
+
return "Cite42 spending limit reached. Adjust your limit at https://www.cite42.dev/app/billing.";
|
|
58
85
|
}
|
|
59
86
|
if (payload.error.code === "insufficient_credits") {
|
|
60
|
-
return "Insufficient Cite42
|
|
87
|
+
return "Insufficient Cite42 balance. Add funds at https://www.cite42.dev/app/billing.";
|
|
61
88
|
}
|
|
62
89
|
}
|
|
63
90
|
} catch {
|
|
@@ -90,8 +117,8 @@ function getApiKey() {
|
|
|
90
117
|
function getBaseUrl() {
|
|
91
118
|
return readFlag("api-base") ?? process.env.CITE42_API_BASE ?? DEFAULT_BASE_URL;
|
|
92
119
|
}
|
|
93
|
-
async function aivPost(path, body) {
|
|
94
|
-
return aivWrite("POST", path, body);
|
|
120
|
+
async function aivPost(path, body, signal) {
|
|
121
|
+
return aivWrite("POST", path, body, signal);
|
|
95
122
|
}
|
|
96
123
|
async function aivPatch(path, body) {
|
|
97
124
|
return aivWrite("PATCH", path, body);
|
|
@@ -108,7 +135,8 @@ async function aivDelete(path) {
|
|
|
108
135
|
}
|
|
109
136
|
return toCite42McpResult(await res.json());
|
|
110
137
|
}
|
|
111
|
-
async function aivWrite(method, path, body) {
|
|
138
|
+
async function aivWrite(method, path, body, signal) {
|
|
139
|
+
signal?.throwIfAborted();
|
|
112
140
|
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
113
141
|
const res = await fetch(url, {
|
|
114
142
|
method,
|
|
@@ -116,7 +144,8 @@ async function aivWrite(method, path, body) {
|
|
|
116
144
|
"Content-Type": "application/json",
|
|
117
145
|
Authorization: `Bearer ${getApiKey()}`
|
|
118
146
|
},
|
|
119
|
-
body: JSON.stringify(body)
|
|
147
|
+
body: JSON.stringify(body),
|
|
148
|
+
signal
|
|
120
149
|
});
|
|
121
150
|
if (!res.ok) {
|
|
122
151
|
const text = await res.text();
|
|
@@ -149,8 +178,8 @@ var SHORT_TEXT_MAX_CHARS = 200;
|
|
|
149
178
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
150
179
|
var MODEL_SELECTION_DESCRIPTION = 'Optional model subset. Omit to query the default surfaces (chatgpt, perplexity, gemini). Pass ["perplexity"] or ["perplexity","claude"] when the user names specific surfaces or wants to reduce cost. The full set is chatgpt, claude, perplexity, gemini, google_ai_overview \u2014 to query all of them, set fullSweep instead of listing them.';
|
|
151
180
|
var FULL_SWEEP_DESCRIPTION = "Query all five AI surfaces instead of the three default ones, adding claude and google_ai_overview. Costs more per call. Use it when the user wants complete cross-model coverage, is comparing surfaces against each other, or asks specifically about Claude or Google AI Overviews. Ignored when models is given.";
|
|
152
|
-
var MCP_USAGE_GUIDANCE = "Covers AI-search, citation, SEO, keyword, trend, and market-research data. If a plan needs more than 20 endpoint calls or may use more than
|
|
153
|
-
var MODEL_PRICING = "Pricing is per selected AI surface. Omitting models queries the three default surfaces (chatgpt, perplexity, gemini); fullSweep: true queries all five, adding claude and google_ai_overview
|
|
181
|
+
var MCP_USAGE_GUIDANCE = "Covers AI-search, citation, SEO, keyword, trend, and market-research data. If a plan needs more than 20 endpoint calls or may use more than $5 USD, estimate the cost in USD and ask the user before continuing. Quote Cite42 costs and balances in USD using cite42Billing or the USD pricing table. Legacy credit fields are for compatibility only. These funds belong to Cite42, not the host or model provider. AI visibility metrics include counts; quote them with percentages, not as a market-wide score. Optional recommendations are a separate paid call, never automatic. After a visibility result, offer recommendations using its requestId; run only when the user requests advice and knows the additional cost. Free: cite42_balance for the remaining balance, cite42_pricing for exact per-tool prices and cost estimates, cite42_usage for recent calls.";
|
|
182
|
+
var MODEL_PRICING = "Pricing is per selected AI surface. Omitting models queries the three default surfaces (chatgpt, perplexity, gemini); fullSweep: true queries all five, adding claude and google_ai_overview at a higher cost. Use the models field when the user names a surface or asks to reduce spending, and call cite42_pricing for exact current rates.";
|
|
154
183
|
var DATA_PRICING = "Data tools have flat per-call prices; call cite42_pricing for exact current rates.";
|
|
155
184
|
var KEYWORDS_PRICING = "Pricing: billed by outcome \u2014 cheapest when the seed has no search volume, more when it has metrics, most with ideas:true. Call cite42_pricing for exact current rates.";
|
|
156
185
|
var WORKFLOW_PRICING = "Pricing: workflow cost is calculated from the tools that run successfully and the selected model rates.";
|
|
@@ -177,16 +206,59 @@ function queryText(description) {
|
|
|
177
206
|
function shortText(description) {
|
|
178
207
|
return import_zod.z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).describe(description);
|
|
179
208
|
}
|
|
209
|
+
var aivRecommendations = (0, import_tools.createTool)({
|
|
210
|
+
id: "cite42_recommendations",
|
|
211
|
+
mcp: readOnly("Recommend AI visibility next steps"),
|
|
212
|
+
description: "Optional evidence-backed AI visibility recommendations. Flat price from cite42_pricing per completed AI report; insufficient evidence and rule-only fallback are free. Supports rankings, compare, citations and sentiment, including usable workflow steps. No crawling, fresh research, keyword advice or tracker activation. " + MCP_USAGE_GUIDANCE,
|
|
213
|
+
inputSchema: import_zod.z.object({
|
|
214
|
+
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
|
+
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."),
|
|
216
|
+
evidence: import_zod.z.array(import_zod.z.object({
|
|
217
|
+
primitive: import_zod.z.enum(["rankings", "compare", "citations", "sentiment"]),
|
|
218
|
+
input: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()),
|
|
219
|
+
result: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()),
|
|
220
|
+
measuredAt: import_zod.z.string().datetime()
|
|
221
|
+
})).min(1).max(20).optional(),
|
|
222
|
+
context: import_zod.z.object({
|
|
223
|
+
brand: import_zod.z.string().trim().min(1).max(200).optional(),
|
|
224
|
+
domain: import_zod.z.string().trim().min(1).max(250).optional(),
|
|
225
|
+
audience: import_zod.z.string().trim().min(1).max(400).optional(),
|
|
226
|
+
goal: import_zod.z.string().trim().min(1).max(400).optional()
|
|
227
|
+
}).optional()
|
|
228
|
+
}).superRefine((input, ctx) => {
|
|
229
|
+
if ([input.requestId, input.trackerRunId, input.evidence].filter((value) => value !== void 0).length !== 1) {
|
|
230
|
+
ctx.addIssue({ code: "custom", message: "Provide exactly one of requestId, trackerRunId or evidence." });
|
|
231
|
+
}
|
|
232
|
+
if (new TextEncoder().encode(JSON.stringify(input)).length > 64 * 1024) {
|
|
233
|
+
ctx.addIssue({ code: "custom", message: "Recommendation input must fit within 64KB." });
|
|
234
|
+
}
|
|
235
|
+
}),
|
|
236
|
+
execute: async (input) => aivPost("/recommendations", input)
|
|
237
|
+
});
|
|
180
238
|
function optionalShortText(description) {
|
|
181
239
|
return import_zod.z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).optional().describe(description);
|
|
182
240
|
}
|
|
183
241
|
var modelsInput = import_zod.z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
184
242
|
var fullSweepInput = import_zod.z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
|
|
185
243
|
var competitorList = import_zod.z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
244
|
+
var aivBalance = (0, import_tools.createTool)({
|
|
245
|
+
id: "cite42_balance",
|
|
246
|
+
mcp: readOnly("Check Cite42 balance", false),
|
|
247
|
+
description: "Check the remaining Cite42 balance for the connected account. Free. " + MCP_USAGE_GUIDANCE,
|
|
248
|
+
inputSchema: import_zod.z.object({}),
|
|
249
|
+
execute: async () => {
|
|
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
|
+
}
|
|
257
|
+
});
|
|
186
258
|
var aivCredits = (0, import_tools.createTool)({
|
|
187
259
|
id: "cite42_credits",
|
|
188
|
-
mcp: readOnly("Check Cite42
|
|
189
|
-
description: "Check the remaining Cite42
|
|
260
|
+
mcp: readOnly("Check Cite42 balance (legacy alias)", false),
|
|
261
|
+
description: "Check the remaining Cite42 balance for the connected account. Free. " + MCP_USAGE_GUIDANCE,
|
|
190
262
|
inputSchema: import_zod.z.object({}),
|
|
191
263
|
execute: async () => {
|
|
192
264
|
const usage = await aivGet("/usage");
|
|
@@ -200,14 +272,16 @@ var aivCredits = (0, import_tools.createTool)({
|
|
|
200
272
|
var aivPricing = (0, import_tools.createTool)({
|
|
201
273
|
id: "cite42_pricing",
|
|
202
274
|
mcp: readOnly("Get Cite42 price table", false),
|
|
203
|
-
description: 'Get the exact Cite42 price table
|
|
204
|
-
inputSchema: import_zod.z.object({
|
|
205
|
-
|
|
275
|
+
description: 'Get the exact Cite42 price table. Pass currency: USD for dollar prices; otherwise read the usd block (legacy fields are compatibility only): per-model rates, flat-rate tools, workflow maximums, and which tools are free. Use it to answer any "how much does X cost" question or to estimate a plan before running it. Free. ' + MCP_USAGE_GUIDANCE,
|
|
276
|
+
inputSchema: import_zod.z.object({
|
|
277
|
+
currency: import_zod.z.enum(["USD", "credits"]).optional().describe("Use USD for dollar prices. Omit only for legacy compatibility.")
|
|
278
|
+
}),
|
|
279
|
+
execute: async (input) => input.currency === "USD" ? aivGet("/pricing?currency=USD") : { ...await aivGet("/pricing"), usd: await aivGet("/pricing?currency=USD") }
|
|
206
280
|
});
|
|
207
281
|
var aivUsage = (0, import_tools.createTool)({
|
|
208
282
|
id: "cite42_usage",
|
|
209
283
|
mcp: readOnly("Get recent Cite42 usage", false),
|
|
210
|
-
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining
|
|
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. " + MCP_USAGE_GUIDANCE,
|
|
211
285
|
inputSchema: import_zod.z.object({}),
|
|
212
286
|
execute: async () => {
|
|
213
287
|
const usage = await aivGet("/usage");
|
|
@@ -221,6 +295,7 @@ var aivUsage = (0, import_tools.createTool)({
|
|
|
221
295
|
endpoint: row.endpoint,
|
|
222
296
|
status: row.status,
|
|
223
297
|
credits: typeof row.costMicro === "number" ? toCredits(row.costMicro) : void 0,
|
|
298
|
+
costUsd: typeof row.costMicro === "number" ? toUsd(row.costMicro) : void 0,
|
|
224
299
|
at: row.createdAt
|
|
225
300
|
}))
|
|
226
301
|
};
|
|
@@ -302,6 +377,23 @@ var aivKeywords = (0, import_tools.createTool)({
|
|
|
302
377
|
}),
|
|
303
378
|
execute: async (input) => aivPost("/keywords", input)
|
|
304
379
|
});
|
|
380
|
+
var aivKeywordsBatch = (0, import_tools.createTool)({
|
|
381
|
+
id: "cite42_keywords_batch",
|
|
382
|
+
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; call cite42_pricing for current rates. ${MCP_USAGE_GUIDANCE}`,
|
|
384
|
+
inputSchema: import_zod.z.object({
|
|
385
|
+
seeds: import_zod.z.array(import_zod.z.string().trim().min(1).max(80).refine(
|
|
386
|
+
(seed) => seed.split(/\s+/).length <= 10,
|
|
387
|
+
"Each keyword must contain at most 10 words."
|
|
388
|
+
)).min(2).max(50).refine(
|
|
389
|
+
(seeds) => new Set(seeds.map((seed) => seed.toLowerCase().replace(/\s+/g, " "))).size === seeds.length,
|
|
390
|
+
"Keywords must be distinct."
|
|
391
|
+
).describe("Two to 50 distinct seed keywords."),
|
|
392
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
393
|
+
language: import_zod.z.string().trim().max(8).optional().describe("Optional ISO-2 language code.")
|
|
394
|
+
}),
|
|
395
|
+
execute: async (input) => aivPost("/keywords/batch", input)
|
|
396
|
+
});
|
|
305
397
|
var aivTrends = (0, import_tools.createTool)({
|
|
306
398
|
id: "cite42_trends",
|
|
307
399
|
mcp: readOnly("Get Google Trends data"),
|
|
@@ -315,14 +407,14 @@ var aivTrends = (0, import_tools.createTool)({
|
|
|
315
407
|
});
|
|
316
408
|
var aivRedditTrends = (0, import_tools.createTool)({
|
|
317
409
|
id: "cite42_reddit_trends",
|
|
318
|
-
mcp: readOnly("
|
|
319
|
-
description: `Reddit
|
|
410
|
+
mcp: readOnly("Find Reddit discussions"),
|
|
411
|
+
description: `Discover relevant Reddit discussions through Gemini Google Search. Returns labeled model summaries, citations and coverage warnings, with no author quotes or quote-based insights. Does not measure growth or read complete comment conversations. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
320
412
|
inputSchema: import_zod.z.object({
|
|
321
413
|
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
322
414
|
subreddits: import_zod.z.array(shortText("Subreddit name without r/.")).max(20).optional().describe("Optional subreddit names, without r/."),
|
|
323
415
|
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Conversation window, e.g. "past_7_days", "past_30_days", "past_90_days".')
|
|
324
416
|
}),
|
|
325
|
-
execute: async (input) => aivPost("/reddit/trends", input)
|
|
417
|
+
execute: async (input, context) => aivPost("/reddit/trends", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
326
418
|
});
|
|
327
419
|
var aivYoutubeTrends = (0, import_tools.createTool)({
|
|
328
420
|
id: "cite42_youtube_trends",
|
|
@@ -350,7 +442,7 @@ var aivFindContentOpportunities = (0, import_tools.createTool)({
|
|
|
350
442
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
351
443
|
channels: import_zod.z.array(channelEnum).optional().describe("Optional data sources to include.")
|
|
352
444
|
}),
|
|
353
|
-
execute: async (input) => aivPost("/workflows/content-opportunities", input)
|
|
445
|
+
execute: async (input, context) => aivPost("/workflows/content-opportunities", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
354
446
|
});
|
|
355
447
|
var aivAnalyzeTopicDemand = (0, import_tools.createTool)({
|
|
356
448
|
id: "cite42_analyze_topic_demand",
|
|
@@ -363,7 +455,7 @@ var aivAnalyzeTopicDemand = (0, import_tools.createTool)({
|
|
|
363
455
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
364
456
|
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
365
457
|
}),
|
|
366
|
-
execute: async (input) => aivPost("/workflows/topic-demand", input)
|
|
458
|
+
execute: async (input, context) => aivPost("/workflows/topic-demand", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
367
459
|
});
|
|
368
460
|
var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
369
461
|
id: "cite42_map_ai_prompts",
|
|
@@ -379,7 +471,7 @@ var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
|
379
471
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
380
472
|
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
381
473
|
}),
|
|
382
|
-
execute: async (input) => aivPost("/workflows/ai-prompt-map", input)
|
|
474
|
+
execute: async (input, context) => aivPost("/workflows/ai-prompt-map", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
383
475
|
});
|
|
384
476
|
var aivAnalyzeCompetitorContent = (0, import_tools.createTool)({
|
|
385
477
|
id: "cite42_analyze_competitor_content",
|
|
@@ -393,7 +485,7 @@ var aivAnalyzeCompetitorContent = (0, import_tools.createTool)({
|
|
|
393
485
|
brand: optionalShortText("Optional primary brand/domain to compare against."),
|
|
394
486
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
395
487
|
}),
|
|
396
|
-
execute: async (input) => aivPost("/workflows/competitor-content", input)
|
|
488
|
+
execute: async (input, context) => aivPost("/workflows/competitor-content", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
397
489
|
});
|
|
398
490
|
var aivFindAiCitationGaps = (0, import_tools.createTool)({
|
|
399
491
|
id: "cite42_find_ai_citation_gaps",
|
|
@@ -409,7 +501,7 @@ var aivFindAiCitationGaps = (0, import_tools.createTool)({
|
|
|
409
501
|
models: modelsInput,
|
|
410
502
|
fullSweep: fullSweepInput
|
|
411
503
|
}),
|
|
412
|
-
execute: async (input) => aivPost("/workflows/ai-citation-gaps", input)
|
|
504
|
+
execute: async (input, context) => aivPost("/workflows/ai-citation-gaps", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
413
505
|
});
|
|
414
506
|
var aivGenerateContentBrief = (0, import_tools.createTool)({
|
|
415
507
|
id: "cite42_generate_content_brief",
|
|
@@ -424,7 +516,7 @@ var aivGenerateContentBrief = (0, import_tools.createTool)({
|
|
|
424
516
|
goal: optionalShortText("Optional content goal, e.g. rank, earn citations, convert."),
|
|
425
517
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
426
518
|
}),
|
|
427
|
-
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
519
|
+
execute: async (input, context) => aivPost("/workflows/content-brief", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
428
520
|
});
|
|
429
521
|
var trackerIdInput = import_zod.z.string().trim().min(1).max(128).describe("Cite42 tracker ID returned by a tracker management or report tool.");
|
|
430
522
|
var trackerCadenceInput = import_zod.z.object({
|
|
@@ -478,7 +570,7 @@ var trackerDefinitionFields = {
|
|
|
478
570
|
"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."
|
|
479
571
|
)
|
|
480
572
|
};
|
|
481
|
-
var TRACKER_BILLING_DESCRIPTION = "Tracker management and stored-report reads are free. Each scheduled or manual run bills one call per prompt at the normal per-model rates; the activation response and tracker record expose the accepted maximum cost per run.";
|
|
573
|
+
var TRACKER_BILLING_DESCRIPTION = "Tracker management and stored-report reads are free. Each scheduled or manual run bills one call per prompt at the normal per-model rates; the activation response and tracker record expose the accepted maximum cost per run. Present the returned USD amounts to users; activation still takes the exact estimatedCostMicroPerRun integer returned by the tracker.";
|
|
482
574
|
var aivTrackerCreate = (0, import_tools.createTool)({
|
|
483
575
|
id: "cite42_tracker_create",
|
|
484
576
|
mcp: mutation("Create Cite42 tracker draft"),
|
|
@@ -509,7 +601,7 @@ var aivTrackerActivate = (0, import_tools.createTool)({
|
|
|
509
601
|
var aivTrackerPause = (0, import_tools.createTool)({
|
|
510
602
|
id: "cite42_tracker_pause",
|
|
511
603
|
mcp: mutation("Pause Cite42 tracker", { idempotentHint: true }),
|
|
512
|
-
description: "Pause future recurring calls for a tracker while preserving its configuration and stored run history. Free
|
|
604
|
+
description: "Pause future recurring calls for a tracker while preserving its configuration and stored run history. Free.",
|
|
513
605
|
inputSchema: import_zod.z.object({ trackerId: trackerIdInput }),
|
|
514
606
|
execute: async ({ trackerId }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/pause`, {})
|
|
515
607
|
});
|
|
@@ -519,7 +611,7 @@ var aivTrackerDelete = (0, import_tools.createTool)({
|
|
|
519
611
|
destructiveHint: true,
|
|
520
612
|
idempotentHint: true
|
|
521
613
|
}),
|
|
522
|
-
description: "Permanently delete a draft or paused tracker, including its saved configuration and run history. Active trackers must be paused first, and deletion is rejected while a run is queued or running. Financial usage and
|
|
614
|
+
description: "Permanently delete a draft or paused tracker, including its saved configuration and run history. Active trackers must be paused first, and deletion is rejected while a run is queued or running. Financial usage and billing ledger records are retained. Free.",
|
|
523
615
|
inputSchema: import_zod.z.object({ trackerId: trackerIdInput }),
|
|
524
616
|
execute: async ({ trackerId }) => aivDelete(`/trackers/${encodeURIComponent(trackerId)}`)
|
|
525
617
|
});
|
|
@@ -539,14 +631,14 @@ var aivTrackerRun = (0, import_tools.createTool)({
|
|
|
539
631
|
var aivTrackers = (0, import_tools.createTool)({
|
|
540
632
|
id: "cite42_trackers",
|
|
541
633
|
mcp: readOnly("List Cite42 trackers", false),
|
|
542
|
-
description: "List the connected account\u2019s trackers with status, cadence, tool configuration, cost estimate, accepted per-run maximum, and next/last run times. Free
|
|
634
|
+
description: "List the connected account\u2019s trackers with status, cadence, tool configuration, cost estimate, accepted per-run maximum, and next/last run times. Free.",
|
|
543
635
|
inputSchema: import_zod.z.object({}),
|
|
544
636
|
execute: async () => aivGet("/trackers")
|
|
545
637
|
});
|
|
546
638
|
var aivTrackerReport = (0, import_tools.createTool)({
|
|
547
639
|
id: "cite42_tracker_report",
|
|
548
640
|
mcp: readOnly("Read Cite42 tracker report", false),
|
|
549
|
-
description: "Read bounded stored run history for one tracker, including per-tool results, changes from the prior comparable observation, statuses, sampled times, and costs. Free
|
|
641
|
+
description: "Read bounded stored run history for one tracker, including per-tool results, changes from the prior comparable observation, statuses, sampled times, and costs. Free.",
|
|
550
642
|
inputSchema: import_zod.z.object({
|
|
551
643
|
trackerId: trackerIdInput,
|
|
552
644
|
limit: import_zod.z.number().int().min(1).max(20).optional().default(8).describe("Most recent runs to return, capped for LLM context.")
|
|
@@ -556,6 +648,7 @@ var aivTrackerReport = (0, import_tools.createTool)({
|
|
|
556
648
|
|
|
557
649
|
// src/server.ts
|
|
558
650
|
var CITE42_TOOLS = {
|
|
651
|
+
cite42_balance: aivBalance,
|
|
559
652
|
cite42_credits: aivCredits,
|
|
560
653
|
cite42_pricing: aivPricing,
|
|
561
654
|
cite42_usage: aivUsage,
|
|
@@ -564,7 +657,9 @@ var CITE42_TOOLS = {
|
|
|
564
657
|
cite42_compare: aivCompare,
|
|
565
658
|
cite42_citations: aivCitations,
|
|
566
659
|
cite42_sentiment: aivSentiment,
|
|
660
|
+
cite42_recommendations: aivRecommendations,
|
|
567
661
|
cite42_keywords: aivKeywords,
|
|
662
|
+
cite42_keywords_batch: aivKeywordsBatch,
|
|
568
663
|
cite42_trends: aivTrends,
|
|
569
664
|
cite42_reddit_trends: aivRedditTrends,
|
|
570
665
|
cite42_youtube_trends: aivYoutubeTrends,
|
|
@@ -600,7 +695,7 @@ if (!resolveApiKey()) {
|
|
|
600
695
|
);
|
|
601
696
|
process.exit(1);
|
|
602
697
|
}
|
|
603
|
-
buildCite42Server("0.
|
|
698
|
+
buildCite42Server("0.6.0").startStdio().catch((error) => {
|
|
604
699
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
605
700
|
process.exit(1);
|
|
606
701
|
});
|
package/dist/stdio.mjs
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
// src/client.ts
|
|
4
4
|
var DEFAULT_BASE_URL = "https://www.cite42.dev/api/v1";
|
|
5
5
|
var MICRO_CREDITS_PER_CREDIT = 1e3;
|
|
6
|
+
function toUsd(micro) {
|
|
7
|
+
const [whole, fraction = ""] = (micro / 1e5).toFixed(5).split(".");
|
|
8
|
+
return `${whole}.${fraction.slice(0, Math.max(2, fraction.replace(/0+$/, "").length))}`;
|
|
9
|
+
}
|
|
10
|
+
function toCite42Balance(micro) {
|
|
11
|
+
return {
|
|
12
|
+
cite42Billing: {
|
|
13
|
+
currency: "USD",
|
|
14
|
+
remainingUsd: toUsd(micro),
|
|
15
|
+
message: `Cite42 balance: $${toUsd(micro)} USD remaining. This balance check was free.`
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
6
19
|
function isJsonObject(value) {
|
|
7
20
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
21
|
}
|
|
@@ -29,19 +42,33 @@ function toCite42McpResult(value) {
|
|
|
29
42
|
if (typeof cost !== "number" || !Number.isFinite(cost) || typeof balance !== "number" || !Number.isFinite(balance)) {
|
|
30
43
|
return result;
|
|
31
44
|
}
|
|
45
|
+
const originalCost = isJsonObject(billing.originalCost) ? billing.originalCost.microCredits : void 0;
|
|
46
|
+
const replay = typeof result.replayOf === "string" && typeof originalCost === "number" && Number.isFinite(originalCost);
|
|
47
|
+
const lowBalanceNotice = typeof warning === "string" ? " Low Cite42 balance." : "";
|
|
48
|
+
const cite42Billing = {
|
|
49
|
+
currency: "USD",
|
|
50
|
+
chargedUsd: toUsd(cost),
|
|
51
|
+
remainingUsd: toUsd(balance),
|
|
52
|
+
...replay ? { originalChargedUsd: toUsd(originalCost) } : {},
|
|
53
|
+
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
|
+
};
|
|
32
55
|
const lowCredits = typeof warning === "string";
|
|
33
56
|
const message = `Cite42 credits: ${formatCredits(cost)} charged; ${formatCredits(balance)} remaining.`;
|
|
34
57
|
return {
|
|
35
58
|
...result,
|
|
59
|
+
cite42Billing,
|
|
60
|
+
// Legacy numeric fields retain their original units for existing integrations.
|
|
36
61
|
cite42Credits: {
|
|
37
62
|
charged: toCredits(cost),
|
|
38
63
|
remaining: toCredits(balance),
|
|
39
|
-
|
|
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
|
|
40
66
|
}
|
|
41
67
|
};
|
|
42
68
|
}
|
|
43
69
|
function toCite42CreditsBalance(balanceMicroCredits) {
|
|
44
70
|
return {
|
|
71
|
+
...toCite42Balance(balanceMicroCredits),
|
|
45
72
|
cite42Credits: {
|
|
46
73
|
remaining: toCredits(balanceMicroCredits),
|
|
47
74
|
message: `Cite42 credits: ${formatCredits(balanceMicroCredits)} remaining. This balance check was free.`
|
|
@@ -53,10 +80,10 @@ function errorMessageForMcp(status, path, body) {
|
|
|
53
80
|
const payload = JSON.parse(body);
|
|
54
81
|
if (isJsonObject(payload) && isJsonObject(payload.error) && typeof payload.error.code === "string") {
|
|
55
82
|
if (payload.error.code === "daily_limit_exceeded" || payload.error.code === "monthly_limit_exceeded") {
|
|
56
|
-
return "Cite42
|
|
83
|
+
return "Cite42 spending limit reached. Adjust your limit at https://www.cite42.dev/app/billing.";
|
|
57
84
|
}
|
|
58
85
|
if (payload.error.code === "insufficient_credits") {
|
|
59
|
-
return "Insufficient Cite42
|
|
86
|
+
return "Insufficient Cite42 balance. Add funds at https://www.cite42.dev/app/billing.";
|
|
60
87
|
}
|
|
61
88
|
}
|
|
62
89
|
} catch {
|
|
@@ -89,8 +116,8 @@ function getApiKey() {
|
|
|
89
116
|
function getBaseUrl() {
|
|
90
117
|
return readFlag("api-base") ?? process.env.CITE42_API_BASE ?? DEFAULT_BASE_URL;
|
|
91
118
|
}
|
|
92
|
-
async function aivPost(path, body) {
|
|
93
|
-
return aivWrite("POST", path, body);
|
|
119
|
+
async function aivPost(path, body, signal) {
|
|
120
|
+
return aivWrite("POST", path, body, signal);
|
|
94
121
|
}
|
|
95
122
|
async function aivPatch(path, body) {
|
|
96
123
|
return aivWrite("PATCH", path, body);
|
|
@@ -107,7 +134,8 @@ async function aivDelete(path) {
|
|
|
107
134
|
}
|
|
108
135
|
return toCite42McpResult(await res.json());
|
|
109
136
|
}
|
|
110
|
-
async function aivWrite(method, path, body) {
|
|
137
|
+
async function aivWrite(method, path, body, signal) {
|
|
138
|
+
signal?.throwIfAborted();
|
|
111
139
|
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
112
140
|
const res = await fetch(url, {
|
|
113
141
|
method,
|
|
@@ -115,7 +143,8 @@ async function aivWrite(method, path, body) {
|
|
|
115
143
|
"Content-Type": "application/json",
|
|
116
144
|
Authorization: `Bearer ${getApiKey()}`
|
|
117
145
|
},
|
|
118
|
-
body: JSON.stringify(body)
|
|
146
|
+
body: JSON.stringify(body),
|
|
147
|
+
signal
|
|
119
148
|
});
|
|
120
149
|
if (!res.ok) {
|
|
121
150
|
const text = await res.text();
|
|
@@ -148,8 +177,8 @@ var SHORT_TEXT_MAX_CHARS = 200;
|
|
|
148
177
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
149
178
|
var MODEL_SELECTION_DESCRIPTION = 'Optional model subset. Omit to query the default surfaces (chatgpt, perplexity, gemini). Pass ["perplexity"] or ["perplexity","claude"] when the user names specific surfaces or wants to reduce cost. The full set is chatgpt, claude, perplexity, gemini, google_ai_overview \u2014 to query all of them, set fullSweep instead of listing them.';
|
|
150
179
|
var FULL_SWEEP_DESCRIPTION = "Query all five AI surfaces instead of the three default ones, adding claude and google_ai_overview. Costs more per call. Use it when the user wants complete cross-model coverage, is comparing surfaces against each other, or asks specifically about Claude or Google AI Overviews. Ignored when models is given.";
|
|
151
|
-
var MCP_USAGE_GUIDANCE = "Covers AI-search, citation, SEO, keyword, trend, and market-research data. If a plan needs more than 20 endpoint calls or may use more than
|
|
152
|
-
var MODEL_PRICING = "Pricing is per selected AI surface. Omitting models queries the three default surfaces (chatgpt, perplexity, gemini); fullSweep: true queries all five, adding claude and google_ai_overview
|
|
180
|
+
var MCP_USAGE_GUIDANCE = "Covers AI-search, citation, SEO, keyword, trend, and market-research data. If a plan needs more than 20 endpoint calls or may use more than $5 USD, estimate the cost in USD and ask the user before continuing. Quote Cite42 costs and balances in USD using cite42Billing or the USD pricing table. Legacy credit fields are for compatibility only. These funds belong to Cite42, not the host or model provider. AI visibility metrics include counts; quote them with percentages, not as a market-wide score. Optional recommendations are a separate paid call, never automatic. After a visibility result, offer recommendations using its requestId; run only when the user requests advice and knows the additional cost. Free: cite42_balance for the remaining balance, cite42_pricing for exact per-tool prices and cost estimates, cite42_usage for recent calls.";
|
|
181
|
+
var MODEL_PRICING = "Pricing is per selected AI surface. Omitting models queries the three default surfaces (chatgpt, perplexity, gemini); fullSweep: true queries all five, adding claude and google_ai_overview at a higher cost. Use the models field when the user names a surface or asks to reduce spending, and call cite42_pricing for exact current rates.";
|
|
153
182
|
var DATA_PRICING = "Data tools have flat per-call prices; call cite42_pricing for exact current rates.";
|
|
154
183
|
var KEYWORDS_PRICING = "Pricing: billed by outcome \u2014 cheapest when the seed has no search volume, more when it has metrics, most with ideas:true. Call cite42_pricing for exact current rates.";
|
|
155
184
|
var WORKFLOW_PRICING = "Pricing: workflow cost is calculated from the tools that run successfully and the selected model rates.";
|
|
@@ -176,16 +205,59 @@ function queryText(description) {
|
|
|
176
205
|
function shortText(description) {
|
|
177
206
|
return z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).describe(description);
|
|
178
207
|
}
|
|
208
|
+
var aivRecommendations = createTool({
|
|
209
|
+
id: "cite42_recommendations",
|
|
210
|
+
mcp: readOnly("Recommend AI visibility next steps"),
|
|
211
|
+
description: "Optional evidence-backed AI visibility recommendations. Flat price from cite42_pricing per completed AI report; insufficient evidence and rule-only fallback are free. Supports rankings, compare, citations and sentiment, including usable workflow steps. No crawling, fresh research, keyword advice or tracker activation. " + MCP_USAGE_GUIDANCE,
|
|
212
|
+
inputSchema: z.object({
|
|
213
|
+
requestId: z.string().trim().min(1).max(128).optional().describe("A requestId from your own successful Cite42 call within the last 30 days."),
|
|
214
|
+
trackerRunId: z.string().trim().min(1).max(128).optional().describe("A specific finished run of your own tracker within the last 30 days."),
|
|
215
|
+
evidence: z.array(z.object({
|
|
216
|
+
primitive: z.enum(["rankings", "compare", "citations", "sentiment"]),
|
|
217
|
+
input: z.record(z.string(), z.unknown()),
|
|
218
|
+
result: z.record(z.string(), z.unknown()),
|
|
219
|
+
measuredAt: z.string().datetime()
|
|
220
|
+
})).min(1).max(20).optional(),
|
|
221
|
+
context: z.object({
|
|
222
|
+
brand: z.string().trim().min(1).max(200).optional(),
|
|
223
|
+
domain: z.string().trim().min(1).max(250).optional(),
|
|
224
|
+
audience: z.string().trim().min(1).max(400).optional(),
|
|
225
|
+
goal: z.string().trim().min(1).max(400).optional()
|
|
226
|
+
}).optional()
|
|
227
|
+
}).superRefine((input, ctx) => {
|
|
228
|
+
if ([input.requestId, input.trackerRunId, input.evidence].filter((value) => value !== void 0).length !== 1) {
|
|
229
|
+
ctx.addIssue({ code: "custom", message: "Provide exactly one of requestId, trackerRunId or evidence." });
|
|
230
|
+
}
|
|
231
|
+
if (new TextEncoder().encode(JSON.stringify(input)).length > 64 * 1024) {
|
|
232
|
+
ctx.addIssue({ code: "custom", message: "Recommendation input must fit within 64KB." });
|
|
233
|
+
}
|
|
234
|
+
}),
|
|
235
|
+
execute: async (input) => aivPost("/recommendations", input)
|
|
236
|
+
});
|
|
179
237
|
function optionalShortText(description) {
|
|
180
238
|
return z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).optional().describe(description);
|
|
181
239
|
}
|
|
182
240
|
var modelsInput = z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
183
241
|
var fullSweepInput = z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
|
|
184
242
|
var competitorList = z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
243
|
+
var aivBalance = createTool({
|
|
244
|
+
id: "cite42_balance",
|
|
245
|
+
mcp: readOnly("Check Cite42 balance", false),
|
|
246
|
+
description: "Check the remaining Cite42 balance for the connected account. Free. " + MCP_USAGE_GUIDANCE,
|
|
247
|
+
inputSchema: z.object({}),
|
|
248
|
+
execute: async () => {
|
|
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
|
+
}
|
|
256
|
+
});
|
|
185
257
|
var aivCredits = createTool({
|
|
186
258
|
id: "cite42_credits",
|
|
187
|
-
mcp: readOnly("Check Cite42
|
|
188
|
-
description: "Check the remaining Cite42
|
|
259
|
+
mcp: readOnly("Check Cite42 balance (legacy alias)", false),
|
|
260
|
+
description: "Check the remaining Cite42 balance for the connected account. Free. " + MCP_USAGE_GUIDANCE,
|
|
189
261
|
inputSchema: z.object({}),
|
|
190
262
|
execute: async () => {
|
|
191
263
|
const usage = await aivGet("/usage");
|
|
@@ -199,14 +271,16 @@ var aivCredits = createTool({
|
|
|
199
271
|
var aivPricing = createTool({
|
|
200
272
|
id: "cite42_pricing",
|
|
201
273
|
mcp: readOnly("Get Cite42 price table", false),
|
|
202
|
-
description: 'Get the exact Cite42 price table
|
|
203
|
-
inputSchema: z.object({
|
|
204
|
-
|
|
274
|
+
description: 'Get the exact Cite42 price table. Pass currency: USD for dollar prices; otherwise read the usd block (legacy fields are compatibility only): per-model rates, flat-rate tools, workflow maximums, and which tools are free. Use it to answer any "how much does X cost" question or to estimate a plan before running it. Free. ' + MCP_USAGE_GUIDANCE,
|
|
275
|
+
inputSchema: z.object({
|
|
276
|
+
currency: z.enum(["USD", "credits"]).optional().describe("Use USD for dollar prices. Omit only for legacy compatibility.")
|
|
277
|
+
}),
|
|
278
|
+
execute: async (input) => input.currency === "USD" ? aivGet("/pricing?currency=USD") : { ...await aivGet("/pricing"), usd: await aivGet("/pricing?currency=USD") }
|
|
205
279
|
});
|
|
206
280
|
var aivUsage = createTool({
|
|
207
281
|
id: "cite42_usage",
|
|
208
282
|
mcp: readOnly("Get recent Cite42 usage", false),
|
|
209
|
-
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining
|
|
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. " + MCP_USAGE_GUIDANCE,
|
|
210
284
|
inputSchema: z.object({}),
|
|
211
285
|
execute: async () => {
|
|
212
286
|
const usage = await aivGet("/usage");
|
|
@@ -220,6 +294,7 @@ var aivUsage = createTool({
|
|
|
220
294
|
endpoint: row.endpoint,
|
|
221
295
|
status: row.status,
|
|
222
296
|
credits: typeof row.costMicro === "number" ? toCredits(row.costMicro) : void 0,
|
|
297
|
+
costUsd: typeof row.costMicro === "number" ? toUsd(row.costMicro) : void 0,
|
|
223
298
|
at: row.createdAt
|
|
224
299
|
}))
|
|
225
300
|
};
|
|
@@ -301,6 +376,23 @@ var aivKeywords = createTool({
|
|
|
301
376
|
}),
|
|
302
377
|
execute: async (input) => aivPost("/keywords", input)
|
|
303
378
|
});
|
|
379
|
+
var aivKeywordsBatch = createTool({
|
|
380
|
+
id: "cite42_keywords_batch",
|
|
381
|
+
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; call cite42_pricing for current rates. ${MCP_USAGE_GUIDANCE}`,
|
|
383
|
+
inputSchema: z.object({
|
|
384
|
+
seeds: z.array(z.string().trim().min(1).max(80).refine(
|
|
385
|
+
(seed) => seed.split(/\s+/).length <= 10,
|
|
386
|
+
"Each keyword must contain at most 10 words."
|
|
387
|
+
)).min(2).max(50).refine(
|
|
388
|
+
(seeds) => new Set(seeds.map((seed) => seed.toLowerCase().replace(/\s+/g, " "))).size === seeds.length,
|
|
389
|
+
"Keywords must be distinct."
|
|
390
|
+
).describe("Two to 50 distinct seed keywords."),
|
|
391
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
392
|
+
language: z.string().trim().max(8).optional().describe("Optional ISO-2 language code.")
|
|
393
|
+
}),
|
|
394
|
+
execute: async (input) => aivPost("/keywords/batch", input)
|
|
395
|
+
});
|
|
304
396
|
var aivTrends = createTool({
|
|
305
397
|
id: "cite42_trends",
|
|
306
398
|
mcp: readOnly("Get Google Trends data"),
|
|
@@ -314,14 +406,14 @@ var aivTrends = createTool({
|
|
|
314
406
|
});
|
|
315
407
|
var aivRedditTrends = createTool({
|
|
316
408
|
id: "cite42_reddit_trends",
|
|
317
|
-
mcp: readOnly("
|
|
318
|
-
description: `Reddit
|
|
409
|
+
mcp: readOnly("Find Reddit discussions"),
|
|
410
|
+
description: `Discover relevant Reddit discussions through Gemini Google Search. Returns labeled model summaries, citations and coverage warnings, with no author quotes or quote-based insights. Does not measure growth or read complete comment conversations. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
319
411
|
inputSchema: z.object({
|
|
320
412
|
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
321
413
|
subreddits: z.array(shortText("Subreddit name without r/.")).max(20).optional().describe("Optional subreddit names, without r/."),
|
|
322
414
|
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Conversation window, e.g. "past_7_days", "past_30_days", "past_90_days".')
|
|
323
415
|
}),
|
|
324
|
-
execute: async (input) => aivPost("/reddit/trends", input)
|
|
416
|
+
execute: async (input, context) => aivPost("/reddit/trends", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
325
417
|
});
|
|
326
418
|
var aivYoutubeTrends = createTool({
|
|
327
419
|
id: "cite42_youtube_trends",
|
|
@@ -349,7 +441,7 @@ var aivFindContentOpportunities = createTool({
|
|
|
349
441
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
350
442
|
channels: z.array(channelEnum).optional().describe("Optional data sources to include.")
|
|
351
443
|
}),
|
|
352
|
-
execute: async (input) => aivPost("/workflows/content-opportunities", input)
|
|
444
|
+
execute: async (input, context) => aivPost("/workflows/content-opportunities", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
353
445
|
});
|
|
354
446
|
var aivAnalyzeTopicDemand = createTool({
|
|
355
447
|
id: "cite42_analyze_topic_demand",
|
|
@@ -362,7 +454,7 @@ var aivAnalyzeTopicDemand = createTool({
|
|
|
362
454
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
363
455
|
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
364
456
|
}),
|
|
365
|
-
execute: async (input) => aivPost("/workflows/topic-demand", input)
|
|
457
|
+
execute: async (input, context) => aivPost("/workflows/topic-demand", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
366
458
|
});
|
|
367
459
|
var aivMapAiPrompts = createTool({
|
|
368
460
|
id: "cite42_map_ai_prompts",
|
|
@@ -378,7 +470,7 @@ var aivMapAiPrompts = createTool({
|
|
|
378
470
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
379
471
|
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
380
472
|
}),
|
|
381
|
-
execute: async (input) => aivPost("/workflows/ai-prompt-map", input)
|
|
473
|
+
execute: async (input, context) => aivPost("/workflows/ai-prompt-map", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
382
474
|
});
|
|
383
475
|
var aivAnalyzeCompetitorContent = createTool({
|
|
384
476
|
id: "cite42_analyze_competitor_content",
|
|
@@ -392,7 +484,7 @@ var aivAnalyzeCompetitorContent = createTool({
|
|
|
392
484
|
brand: optionalShortText("Optional primary brand/domain to compare against."),
|
|
393
485
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
394
486
|
}),
|
|
395
|
-
execute: async (input) => aivPost("/workflows/competitor-content", input)
|
|
487
|
+
execute: async (input, context) => aivPost("/workflows/competitor-content", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
396
488
|
});
|
|
397
489
|
var aivFindAiCitationGaps = createTool({
|
|
398
490
|
id: "cite42_find_ai_citation_gaps",
|
|
@@ -408,7 +500,7 @@ var aivFindAiCitationGaps = createTool({
|
|
|
408
500
|
models: modelsInput,
|
|
409
501
|
fullSweep: fullSweepInput
|
|
410
502
|
}),
|
|
411
|
-
execute: async (input) => aivPost("/workflows/ai-citation-gaps", input)
|
|
503
|
+
execute: async (input, context) => aivPost("/workflows/ai-citation-gaps", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
412
504
|
});
|
|
413
505
|
var aivGenerateContentBrief = createTool({
|
|
414
506
|
id: "cite42_generate_content_brief",
|
|
@@ -423,7 +515,7 @@ var aivGenerateContentBrief = createTool({
|
|
|
423
515
|
goal: optionalShortText("Optional content goal, e.g. rank, earn citations, convert."),
|
|
424
516
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
425
517
|
}),
|
|
426
|
-
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
518
|
+
execute: async (input, context) => aivPost("/workflows/content-brief", input, context?.abortSignal ?? context?.mcp?.extra.signal)
|
|
427
519
|
});
|
|
428
520
|
var trackerIdInput = z.string().trim().min(1).max(128).describe("Cite42 tracker ID returned by a tracker management or report tool.");
|
|
429
521
|
var trackerCadenceInput = z.object({
|
|
@@ -477,7 +569,7 @@ var trackerDefinitionFields = {
|
|
|
477
569
|
"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."
|
|
478
570
|
)
|
|
479
571
|
};
|
|
480
|
-
var TRACKER_BILLING_DESCRIPTION = "Tracker management and stored-report reads are free. Each scheduled or manual run bills one call per prompt at the normal per-model rates; the activation response and tracker record expose the accepted maximum cost per run.";
|
|
572
|
+
var TRACKER_BILLING_DESCRIPTION = "Tracker management and stored-report reads are free. Each scheduled or manual run bills one call per prompt at the normal per-model rates; the activation response and tracker record expose the accepted maximum cost per run. Present the returned USD amounts to users; activation still takes the exact estimatedCostMicroPerRun integer returned by the tracker.";
|
|
481
573
|
var aivTrackerCreate = createTool({
|
|
482
574
|
id: "cite42_tracker_create",
|
|
483
575
|
mcp: mutation("Create Cite42 tracker draft"),
|
|
@@ -508,7 +600,7 @@ var aivTrackerActivate = createTool({
|
|
|
508
600
|
var aivTrackerPause = createTool({
|
|
509
601
|
id: "cite42_tracker_pause",
|
|
510
602
|
mcp: mutation("Pause Cite42 tracker", { idempotentHint: true }),
|
|
511
|
-
description: "Pause future recurring calls for a tracker while preserving its configuration and stored run history. Free
|
|
603
|
+
description: "Pause future recurring calls for a tracker while preserving its configuration and stored run history. Free.",
|
|
512
604
|
inputSchema: z.object({ trackerId: trackerIdInput }),
|
|
513
605
|
execute: async ({ trackerId }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/pause`, {})
|
|
514
606
|
});
|
|
@@ -518,7 +610,7 @@ var aivTrackerDelete = createTool({
|
|
|
518
610
|
destructiveHint: true,
|
|
519
611
|
idempotentHint: true
|
|
520
612
|
}),
|
|
521
|
-
description: "Permanently delete a draft or paused tracker, including its saved configuration and run history. Active trackers must be paused first, and deletion is rejected while a run is queued or running. Financial usage and
|
|
613
|
+
description: "Permanently delete a draft or paused tracker, including its saved configuration and run history. Active trackers must be paused first, and deletion is rejected while a run is queued or running. Financial usage and billing ledger records are retained. Free.",
|
|
522
614
|
inputSchema: z.object({ trackerId: trackerIdInput }),
|
|
523
615
|
execute: async ({ trackerId }) => aivDelete(`/trackers/${encodeURIComponent(trackerId)}`)
|
|
524
616
|
});
|
|
@@ -538,14 +630,14 @@ var aivTrackerRun = createTool({
|
|
|
538
630
|
var aivTrackers = createTool({
|
|
539
631
|
id: "cite42_trackers",
|
|
540
632
|
mcp: readOnly("List Cite42 trackers", false),
|
|
541
|
-
description: "List the connected account\u2019s trackers with status, cadence, tool configuration, cost estimate, accepted per-run maximum, and next/last run times. Free
|
|
633
|
+
description: "List the connected account\u2019s trackers with status, cadence, tool configuration, cost estimate, accepted per-run maximum, and next/last run times. Free.",
|
|
542
634
|
inputSchema: z.object({}),
|
|
543
635
|
execute: async () => aivGet("/trackers")
|
|
544
636
|
});
|
|
545
637
|
var aivTrackerReport = createTool({
|
|
546
638
|
id: "cite42_tracker_report",
|
|
547
639
|
mcp: readOnly("Read Cite42 tracker report", false),
|
|
548
|
-
description: "Read bounded stored run history for one tracker, including per-tool results, changes from the prior comparable observation, statuses, sampled times, and costs. Free
|
|
640
|
+
description: "Read bounded stored run history for one tracker, including per-tool results, changes from the prior comparable observation, statuses, sampled times, and costs. Free.",
|
|
549
641
|
inputSchema: z.object({
|
|
550
642
|
trackerId: trackerIdInput,
|
|
551
643
|
limit: z.number().int().min(1).max(20).optional().default(8).describe("Most recent runs to return, capped for LLM context.")
|
|
@@ -555,6 +647,7 @@ var aivTrackerReport = createTool({
|
|
|
555
647
|
|
|
556
648
|
// src/server.ts
|
|
557
649
|
var CITE42_TOOLS = {
|
|
650
|
+
cite42_balance: aivBalance,
|
|
558
651
|
cite42_credits: aivCredits,
|
|
559
652
|
cite42_pricing: aivPricing,
|
|
560
653
|
cite42_usage: aivUsage,
|
|
@@ -563,7 +656,9 @@ var CITE42_TOOLS = {
|
|
|
563
656
|
cite42_compare: aivCompare,
|
|
564
657
|
cite42_citations: aivCitations,
|
|
565
658
|
cite42_sentiment: aivSentiment,
|
|
659
|
+
cite42_recommendations: aivRecommendations,
|
|
566
660
|
cite42_keywords: aivKeywords,
|
|
661
|
+
cite42_keywords_batch: aivKeywordsBatch,
|
|
567
662
|
cite42_trends: aivTrends,
|
|
568
663
|
cite42_reddit_trends: aivRedditTrends,
|
|
569
664
|
cite42_youtube_trends: aivYoutubeTrends,
|
|
@@ -599,7 +694,7 @@ if (!resolveApiKey()) {
|
|
|
599
694
|
);
|
|
600
695
|
process.exit(1);
|
|
601
696
|
}
|
|
602
|
-
buildCite42Server("0.
|
|
697
|
+
buildCite42Server("0.6.0").startStdio().catch((error) => {
|
|
603
698
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
604
699
|
process.exit(1);
|
|
605
700
|
});
|
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.6.0",
|
|
5
5
|
"description": "Cite42 MCP server: query the models behind ChatGPT, Claude, Perplexity & Gemini, plus Google AI Overviews, for brand rankings, AI citations, SEO keyword data, and Reddit/YouTube trends, and schedule 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",
|
|
@@ -16,21 +16,24 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsup && chmod +x dist/stdio.mjs",
|
|
18
18
|
"dev": "tsup --watch",
|
|
19
|
+
"inspect": "npm run build && node scripts/inspect.mjs",
|
|
20
|
+
"test:stdio": "npm run build && node --test tests/stdio.test.mjs",
|
|
19
21
|
"test": "vitest run",
|
|
20
22
|
"test:watch": "vitest",
|
|
21
|
-
"prepublishOnly": "npm run build"
|
|
23
|
+
"prepublishOnly": "npm run build && npm run audit:security",
|
|
24
|
+
"audit:security": "npm audit --omit=dev --audit-level=high"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@mastra/core": "^1.
|
|
25
|
-
"@mastra/mcp": "^1.
|
|
27
|
+
"@mastra/core": "^1.68.0",
|
|
28
|
+
"@mastra/mcp": "^1.18.0",
|
|
26
29
|
"zod": "^4.4.3"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
-
"@types/node": "^
|
|
33
|
+
"@types/node": "^22.19.0",
|
|
31
34
|
"tsup": "^8.0.0",
|
|
32
35
|
"typescript": "^5.9.3",
|
|
33
|
-
"vitest": "^4.1.
|
|
36
|
+
"vitest": "^4.1.11"
|
|
34
37
|
},
|
|
35
38
|
"publishConfig": {
|
|
36
39
|
"access": "public"
|
|
@@ -66,12 +69,15 @@
|
|
|
66
69
|
"scheduled-monitoring"
|
|
67
70
|
],
|
|
68
71
|
"engines": {
|
|
69
|
-
"node": ">=
|
|
72
|
+
"node": ">=22.13.0"
|
|
70
73
|
},
|
|
71
74
|
"author": "Cite42 <support@cite42.dev> (https://www.cite42.dev)",
|
|
72
75
|
"homepage": "https://www.cite42.dev",
|
|
73
76
|
"bugs": {
|
|
74
77
|
"email": "support@cite42.dev",
|
|
75
78
|
"url": "https://www.cite42.dev/app/support"
|
|
79
|
+
},
|
|
80
|
+
"overrides": {
|
|
81
|
+
"esbuild": "^0.28.2"
|
|
76
82
|
}
|
|
77
83
|
}
|