@cite42/mcp 0.1.16 → 0.3.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 +31 -10
- package/dist/stdio.cjs +174 -12
- package/dist/stdio.mjs +174 -12
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ Once connected, Claude Code, Claude Desktop, Codex CLI, or Cursor can:
|
|
|
6
6
|
|
|
7
7
|
- **Query AI models** (ChatGPT, Claude, Perplexity, Gemini, Google AI Overviews) and see each model's answer and the sources it cites
|
|
8
8
|
- **Track brand visibility** in AI answers: rankings, head-to-head comparisons, and sentiment
|
|
9
|
+
- **Schedule daily or weekly trackers**: one measurement — rankings, comparisons, citations, or sentiment — over a list of prompts, with email when something changes, then ask your agent for stored run history
|
|
9
10
|
- **Pull SEO data**: keyword search volume, CPC, competition, and Google Trends
|
|
10
11
|
- **Mine Reddit and YouTube** for audience questions, pain points, and rising content
|
|
11
12
|
- **Run full research workflows**: content gaps, topic demand, AI prompt maps, competitor analysis, and content briefs in a single call
|
|
@@ -91,7 +92,7 @@ You do not need to keep `npx -y @cite42/mcp` running in a separate terminal. Loc
|
|
|
91
92
|
- **Claude Code:** run `claude mcp list` and check that `cite42` shows as ✔ connected. Or type `/mcp` inside a session.
|
|
92
93
|
- **Claude Desktop:** open **Settings → Connectors** and confirm Cite42 is connected.
|
|
93
94
|
- **Codex CLI:** run `codex mcp list`.
|
|
94
|
-
- **Cursor:** look for `cite42` in the MCP/tools indicator.
|
|
95
|
+
- **Cursor:** look for `cite42` in the MCP/tools indicator. 26 tools should be listed.
|
|
95
96
|
|
|
96
97
|
If it doesn't appear, see [Troubleshooting](#troubleshooting).
|
|
97
98
|
|
|
@@ -104,6 +105,7 @@ After connecting or restarting, just ask in plain language. Your agent picks the
|
|
|
104
105
|
- *"Get search volume, CPC, and related keywords for 'ai seo tools'."*
|
|
105
106
|
- *"What are people on Reddit complaining about regarding email marketing tools?"*
|
|
106
107
|
- *"Run a content brief for the topic 'ai website builder'."*
|
|
108
|
+
- *"Map the prompts worth tracking for our category, then create a weekly Cite42 rankings tracker for the best five. Show me the estimated credits per run before activating it."*
|
|
107
109
|
|
|
108
110
|
### Example: what a session looks like
|
|
109
111
|
|
|
@@ -113,18 +115,26 @@ After connecting or restarting, just ask in plain language. Your agent picks the
|
|
|
113
115
|
|
|
114
116
|
**Your agent calls** `cite42_citations` **and answers something like:**
|
|
115
117
|
|
|
116
|
-
> Across ChatGPT,
|
|
118
|
+
> Across ChatGPT, Perplexity, and Gemini, the most-cited sources are:
|
|
117
119
|
>
|
|
118
|
-
> 1. **freshbooks.com** (cited by
|
|
119
|
-
> 2. **wave.com** (cited by 3
|
|
120
|
-
> 3. **zoho.com/invoice** (cited by 3
|
|
121
|
-
> 4. **bonsai.com** (cited by
|
|
120
|
+
> 1. **freshbooks.com** (cited by 3/3 models)
|
|
121
|
+
> 2. **wave.com** (cited by 2/3 models)
|
|
122
|
+
> 3. **zoho.com/invoice** (cited by 2/3 models)
|
|
123
|
+
> 4. **bonsai.com** (cited by 1/3 models)
|
|
122
124
|
>
|
|
123
125
|
> Your domain wasn't cited by any model. FreshBooks is cited mainly via comparison/"best of" listicles. A citation-gap analysis (`cite42_find_ai_citation_gaps`) can show which queries you're missing from.
|
|
124
126
|
|
|
125
|
-
The exact brands and sources change by query and model. A good result should show the tool used, model/source evidence, and a short summary you can act on.
|
|
127
|
+
That is the three default surfaces; ask for `fullSweep` to add Claude and Google AI Overviews. The exact brands and sources change by query and model. A good result should show the tool used, model/source evidence, and a short summary you can act on.
|
|
126
128
|
|
|
127
|
-
##
|
|
129
|
+
## Tools
|
|
130
|
+
|
|
131
|
+
Cite42 exposes 26 MCP tools: 3 free account tools, 9 primitive data tools, 6 research workflows, and 8 tracker tools.
|
|
132
|
+
|
|
133
|
+
### Account & pricing (free)
|
|
134
|
+
|
|
135
|
+
- `cite42_credits`: check the connected account's remaining Cite42 credit balance.
|
|
136
|
+
- `cite42_pricing`: get current per-tool prices and estimate a planned set of calls.
|
|
137
|
+
- `cite42_usage`: read recent call status and cost together with the remaining balance.
|
|
128
138
|
|
|
129
139
|
### AI search visibility
|
|
130
140
|
|
|
@@ -145,11 +155,22 @@ The exact brands and sources change by query and model. A good result should sho
|
|
|
145
155
|
|
|
146
156
|
- `cite42_find_content_opportunities`: find content gaps across AI search, keywords, trends, Reddit, YouTube, and citations.
|
|
147
157
|
- `cite42_analyze_topic_demand`: combine keyword data, trends, AI answers, and audience conversations for a topic.
|
|
148
|
-
- `cite42_map_ai_prompts`: discover and cluster buyer prompts worth tracking in AI search.
|
|
158
|
+
- `cite42_map_ai_prompts`: discover and cluster buyer prompts worth tracking in AI search. Its prompts are what `cite42_tracker_create` takes, so the two chain naturally.
|
|
149
159
|
- `cite42_analyze_competitor_content`: analyze AI rankings, comparisons, citations, and topic coverage for competitors.
|
|
150
160
|
- `cite42_find_ai_citation_gaps`: find queries where AI answers cite competitors but not your brand, domain, or URL.
|
|
151
161
|
- `cite42_generate_content_brief`: collect AI answers, citations, keyword demand, and social signals into brief-ready data.
|
|
152
162
|
|
|
163
|
+
### Trackers
|
|
164
|
+
|
|
165
|
+
- `cite42_tracker_create`: create a draft daily or weekly tracker — one measurement applied to a list of prompts. No prompt list yet? Run `cite42_map_ai_prompts` first. Creating a draft is free and does not schedule calls.
|
|
166
|
+
- `cite42_tracker_update`: replace the whole tracker definition — name, cadence, measurement, prompts and configuration. A tracker read returns the same shape, so read it, change what you need, and send it back. Updating is free.
|
|
167
|
+
- `cite42_tracker_activate`: activate or resume recurring runs. You must pass the exact `estimatedCostMicroPerRun` from the latest tracker record as `maxCostMicroPerRun`; activation authorizes billed scheduled runs until the tracker is paused. The response carries the balance, the cost per run and `runsDryAt` — the date the balance stops covering your active trackers — and activation is refused when the balance cannot cover even the next run.
|
|
168
|
+
- `cite42_tracker_pause`: stop future scheduled runs while preserving configuration and history. Pausing is free.
|
|
169
|
+
- `cite42_tracker_delete`: permanently delete a draft or paused tracker and its run history. Active trackers must be paused first, and deletion is rejected while a run is queued or running. Financial usage records remain available.
|
|
170
|
+
- `cite42_tracker_run`: queue one manual run of every saved prompt. Each prompt is billed as its own primitive call at the normal rate.
|
|
171
|
+
- `cite42_trackers`: list trackers, schedules, statuses, cost estimates, and last/next run times. Reading the list is free.
|
|
172
|
+
- `cite42_tracker_report`: read bounded stored run history with results, changes, status, sampled time, and costs (latest 8 by default, up to 20). Reading stored reports is free.
|
|
173
|
+
|
|
153
174
|
## Configuration
|
|
154
175
|
|
|
155
176
|
- `CITE42_API_KEY`: required. Your API key from [www.cite42.dev/app/keys](https://www.cite42.dev/app/keys).
|
|
@@ -163,7 +184,7 @@ npx -y @cite42/mcp --api-key=cite42_live_your_key_here --api-base=https://www.ci
|
|
|
163
184
|
|
|
164
185
|
## Billing
|
|
165
186
|
|
|
166
|
-
|
|
187
|
+
Primitive and workflow calls consume Cite42 credits from your account. 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 credits per run and authorizes recurring billed calls until you pause it. Calls fail with a clear error if the API key is missing or credits run out. See [www.cite42.dev/pricing](https://www.cite42.dev/pricing).
|
|
167
188
|
|
|
168
189
|
## Troubleshooting
|
|
169
190
|
|
package/dist/stdio.cjs
CHANGED
|
@@ -87,9 +87,27 @@ function getBaseUrl() {
|
|
|
87
87
|
return readFlag("api-base") ?? process.env.CITE42_API_BASE ?? DEFAULT_BASE_URL;
|
|
88
88
|
}
|
|
89
89
|
async function aivPost(path, body) {
|
|
90
|
+
return aivWrite("POST", path, body);
|
|
91
|
+
}
|
|
92
|
+
async function aivPatch(path, body) {
|
|
93
|
+
return aivWrite("PATCH", path, body);
|
|
94
|
+
}
|
|
95
|
+
async function aivDelete(path) {
|
|
96
|
+
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
97
|
+
const res = await fetch(url, {
|
|
98
|
+
method: "DELETE",
|
|
99
|
+
headers: { Authorization: `Bearer ${getApiKey()}` }
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok) {
|
|
102
|
+
const body = await res.text();
|
|
103
|
+
throw new Error(errorMessageForMcp(res.status, path, body));
|
|
104
|
+
}
|
|
105
|
+
return toCite42McpResult(await res.json());
|
|
106
|
+
}
|
|
107
|
+
async function aivWrite(method, path, body) {
|
|
90
108
|
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
91
109
|
const res = await fetch(url, {
|
|
92
|
-
method
|
|
110
|
+
method,
|
|
93
111
|
headers: {
|
|
94
112
|
"Content-Type": "application/json",
|
|
95
113
|
Authorization: `Bearer ${getApiKey()}`
|
|
@@ -125,9 +143,10 @@ var modelEnum = import_zod.z.enum(["chatgpt", "claude", "perplexity", "gemini",
|
|
|
125
143
|
var QUERY_MAX_CHARS = 2e3;
|
|
126
144
|
var SHORT_TEXT_MAX_CHARS = 200;
|
|
127
145
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
128
|
-
var MODEL_SELECTION_DESCRIPTION = 'Optional model subset. Omit to query
|
|
146
|
+
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.';
|
|
147
|
+
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.";
|
|
129
148
|
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 500 Cite42 credits, estimate the credits and ask the user before continuing. When a result contains cite42Credits, that figure is Cite42 credits, not a model or host balance. Free (0 credits): cite42_credits for the remaining balance, cite42_pricing for exact per-tool prices and cost estimates, cite42_usage for recent calls.";
|
|
130
|
-
var MODEL_PRICING = "Pricing is per selected AI surface
|
|
149
|
+
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 for more credits. Use the models field when the user names a surface or asks to reduce credit use, and call cite42_pricing for exact current rates.";
|
|
131
150
|
var DATA_PRICING = "Data tools have flat per-call prices; call cite42_pricing for exact current rates.";
|
|
132
151
|
var WORKFLOW_PRICING = "Pricing: workflow cost is calculated from the primitive endpoints that run successfully and the selected model rates.";
|
|
133
152
|
function workflowDescription(description) {
|
|
@@ -136,6 +155,17 @@ function workflowDescription(description) {
|
|
|
136
155
|
function readOnly(title, openWorldHint = true) {
|
|
137
156
|
return { annotations: { title, readOnlyHint: true, openWorldHint } };
|
|
138
157
|
}
|
|
158
|
+
function mutation(title, options = {}) {
|
|
159
|
+
return {
|
|
160
|
+
annotations: {
|
|
161
|
+
title,
|
|
162
|
+
readOnlyHint: false,
|
|
163
|
+
destructiveHint: options.destructiveHint ?? false,
|
|
164
|
+
openWorldHint: options.openWorldHint ?? false,
|
|
165
|
+
...options.idempotentHint === void 0 ? {} : { idempotentHint: options.idempotentHint }
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
139
169
|
function queryText(description) {
|
|
140
170
|
return import_zod.z.string().trim().min(1).max(QUERY_MAX_CHARS).describe(description);
|
|
141
171
|
}
|
|
@@ -146,6 +176,7 @@ function optionalShortText(description) {
|
|
|
146
176
|
return import_zod.z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).optional().describe(description);
|
|
147
177
|
}
|
|
148
178
|
var modelsInput = import_zod.z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
179
|
+
var fullSweepInput = import_zod.z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
|
|
149
180
|
var competitorList = import_zod.z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
150
181
|
var aivCredits = (0, import_tools.createTool)({
|
|
151
182
|
id: "cite42_credits",
|
|
@@ -196,7 +227,8 @@ var aivSearch = (0, import_tools.createTool)({
|
|
|
196
227
|
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} ${MCP_USAGE_GUIDANCE}`,
|
|
197
228
|
inputSchema: import_zod.z.object({
|
|
198
229
|
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
199
|
-
models: modelsInput
|
|
230
|
+
models: modelsInput,
|
|
231
|
+
fullSweep: fullSweepInput
|
|
200
232
|
}),
|
|
201
233
|
execute: async (input) => aivPost("/search", input)
|
|
202
234
|
});
|
|
@@ -207,7 +239,8 @@ var aivCitations = (0, import_tools.createTool)({
|
|
|
207
239
|
inputSchema: import_zod.z.object({
|
|
208
240
|
query: queryText("The concise query to look up cited sources for."),
|
|
209
241
|
url: import_zod.z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
|
|
210
|
-
models: modelsInput
|
|
242
|
+
models: modelsInput,
|
|
243
|
+
fullSweep: fullSweepInput
|
|
211
244
|
}),
|
|
212
245
|
execute: async (input) => aivPost("/citations", input)
|
|
213
246
|
});
|
|
@@ -218,7 +251,8 @@ var aivRankings = (0, import_tools.createTool)({
|
|
|
218
251
|
inputSchema: import_zod.z.object({
|
|
219
252
|
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
220
253
|
competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
|
|
221
|
-
models: modelsInput
|
|
254
|
+
models: modelsInput,
|
|
255
|
+
fullSweep: fullSweepInput
|
|
222
256
|
}),
|
|
223
257
|
execute: async (input) => aivPost("/rankings", input)
|
|
224
258
|
});
|
|
@@ -230,7 +264,8 @@ var aivCompare = (0, import_tools.createTool)({
|
|
|
230
264
|
query: queryText("The concise query to compare the brand and competitors on."),
|
|
231
265
|
brand: shortText("Your brand, ranked alongside the competitors."),
|
|
232
266
|
competitors: import_zod.z.array(shortText("Competitor brand/domain name.")).nonempty().max(10).describe("Competitor brand/domain names (up to 10) to rank against your brand."),
|
|
233
|
-
models: modelsInput
|
|
267
|
+
models: modelsInput,
|
|
268
|
+
fullSweep: fullSweepInput
|
|
234
269
|
}),
|
|
235
270
|
execute: async (input) => aivPost("/compare", input)
|
|
236
271
|
});
|
|
@@ -241,7 +276,8 @@ var aivSentiment = (0, import_tools.createTool)({
|
|
|
241
276
|
inputSchema: import_zod.z.object({
|
|
242
277
|
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
243
278
|
brand: shortText("The brand to score sentiment for in each model answer."),
|
|
244
|
-
models: modelsInput
|
|
279
|
+
models: modelsInput,
|
|
280
|
+
fullSweep: fullSweepInput
|
|
245
281
|
}),
|
|
246
282
|
execute: async (input) => aivPost("/sentiment", input)
|
|
247
283
|
});
|
|
@@ -325,7 +361,7 @@ var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
|
325
361
|
id: "cite42_map_ai_prompts",
|
|
326
362
|
mcp: readOnly("Map buyer prompts for AI search"),
|
|
327
363
|
description: workflowDescription(
|
|
328
|
-
"Run the AI-prompt-map workflow: discover and cluster buyer prompts worth tracking from AI search, SEO demand, trends, citations, Reddit, and YouTube data."
|
|
364
|
+
"Run the AI-prompt-map workflow: discover and cluster buyer prompts worth tracking from AI search, SEO demand, trends, citations, Reddit, and YouTube data. The prompts it returns are exactly what cite42_tracker_create takes, so offer to start tracking the best of them once the user has seen the map."
|
|
329
365
|
),
|
|
330
366
|
inputSchema: import_zod.z.object({
|
|
331
367
|
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
@@ -362,7 +398,8 @@ var aivFindAiCitationGaps = (0, import_tools.createTool)({
|
|
|
362
398
|
brand: shortText("Brand or domain that should earn citations."),
|
|
363
399
|
url: import_zod.z.string().trim().max(500).optional().describe("Optional exact URL to check for citations."),
|
|
364
400
|
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
365
|
-
models: modelsInput
|
|
401
|
+
models: modelsInput,
|
|
402
|
+
fullSweep: fullSweepInput
|
|
366
403
|
}),
|
|
367
404
|
execute: async (input) => aivPost("/workflows/ai-citation-gaps", input)
|
|
368
405
|
});
|
|
@@ -381,6 +418,123 @@ var aivGenerateContentBrief = (0, import_tools.createTool)({
|
|
|
381
418
|
}),
|
|
382
419
|
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
383
420
|
});
|
|
421
|
+
var trackerIdInput = import_zod.z.string().trim().min(1).max(128).describe("Cite42 tracker ID returned by a tracker management or report tool.");
|
|
422
|
+
var trackerCadenceInput = import_zod.z.object({
|
|
423
|
+
frequency: import_zod.z.enum(["daily", "weekly"]),
|
|
424
|
+
timezone: import_zod.z.string().trim().min(1).max(100).refine((timezone) => {
|
|
425
|
+
try {
|
|
426
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
427
|
+
return true;
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}, "Timezone must be a valid IANA timezone.").describe("IANA timezone, for example Europe/London or America/New_York."),
|
|
432
|
+
time: import_zod.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/).describe("Local run time in 24-hour HH:mm format."),
|
|
433
|
+
weekday: import_zod.z.number().int().min(0).max(6).optional().describe("Weekly day, where 0 is Sunday and 6 is Saturday.")
|
|
434
|
+
}).superRefine((cadence, context) => {
|
|
435
|
+
if (cadence.frequency === "weekly" && cadence.weekday === void 0) {
|
|
436
|
+
context.addIssue({
|
|
437
|
+
code: "custom",
|
|
438
|
+
path: ["weekday"],
|
|
439
|
+
message: "weekday is required for a weekly tracker"
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
var trackerPrimitiveInput = import_zod.z.enum(["rankings", "compare", "citations", "sentiment"]).describe(
|
|
444
|
+
"What to measure on every prompt: rankings (which brands get named), compare (the brand against named competitors), citations (which sources are cited), or sentiment (how one brand is described). One measurement per tracker."
|
|
445
|
+
);
|
|
446
|
+
var trackerPromptsInput = import_zod.z.array(queryText("A prompt to run on every scheduled run.")).min(1).max(25).describe(
|
|
447
|
+
"Prompts to track, up to 25. Each is billed as its own call on every run. Keep the wording stable so runs stay comparable, avoid dates, and do not repeat a prompt."
|
|
448
|
+
);
|
|
449
|
+
var trackerDefinitionFields = {
|
|
450
|
+
name: import_zod.z.string().trim().min(1).max(100).describe("Human-readable tracker name."),
|
|
451
|
+
cadence: trackerCadenceInput,
|
|
452
|
+
primitive: trackerPrimitiveInput,
|
|
453
|
+
prompts: trackerPromptsInput,
|
|
454
|
+
brand: import_zod.z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).nullish().describe("Required for compare and sentiment. Not used by rankings or citations."),
|
|
455
|
+
competitors: import_zod.z.array(shortText("Brand or domain name.")).max(20).nullish().describe(
|
|
456
|
+
"Required for rankings (the brands to track, including your own) and compare (the rivals to rank against, up to 10). Not used by citations or sentiment."
|
|
457
|
+
),
|
|
458
|
+
url: import_zod.z.string().trim().min(1).max(500).nullish().describe("Citations only: a URL to report on as cited or not cited."),
|
|
459
|
+
models: import_zod.z.array(modelEnum).nonempty().nullish().describe(
|
|
460
|
+
"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."
|
|
461
|
+
)
|
|
462
|
+
};
|
|
463
|
+
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.";
|
|
464
|
+
var aivTrackerCreate = (0, import_tools.createTool)({
|
|
465
|
+
id: "cite42_tracker_create",
|
|
466
|
+
mcp: mutation("Create Cite42 tracker draft"),
|
|
467
|
+
description: "Create a draft daily or weekly 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. When the user has no prompt list yet, run cite42_map_ai_prompts first and track the prompts it recommends. Creation does not start recurring calls. " + TRACKER_BILLING_DESCRIPTION,
|
|
468
|
+
inputSchema: import_zod.z.object(trackerDefinitionFields),
|
|
469
|
+
execute: async (input) => aivPost("/trackers", input)
|
|
470
|
+
});
|
|
471
|
+
var aivTrackerUpdate = (0, import_tools.createTool)({
|
|
472
|
+
id: "cite42_tracker_update",
|
|
473
|
+
mcp: mutation("Update Cite42 tracker", { destructiveHint: true }),
|
|
474
|
+
description: `Replace a tracker definition: name, cadence, measurement, prompts and configuration. Send the complete definition, not a partial patch \u2014 a tracker read returns this exact shape, so read it, change what you need, and send it back. An edit returns the tracker to draft so its new recurring cost can be confirmed. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
475
|
+
inputSchema: import_zod.z.object({ trackerId: trackerIdInput, ...trackerDefinitionFields }),
|
|
476
|
+
execute: async ({ trackerId, ...input }) => aivPatch(`/trackers/${encodeURIComponent(trackerId)}`, input)
|
|
477
|
+
});
|
|
478
|
+
var aivTrackerActivate = (0, import_tools.createTool)({
|
|
479
|
+
id: "cite42_tracker_activate",
|
|
480
|
+
mcp: mutation("Activate Cite42 tracker", {
|
|
481
|
+
openWorldHint: true,
|
|
482
|
+
idempotentHint: true
|
|
483
|
+
}),
|
|
484
|
+
description: `Activate or resume recurring daily or weekly primitive calls. maxCostMicroPerRun must equal the tracker estimate being accepted; activation authorizes future billed calls until the tracker is paused. The response carries a billing block with the balance, the cost per run, and runsDryAt \u2014 the date the balance stops covering the account's active trackers \u2014 so relay it. Activation is refused when the balance cannot cover even the next run; tell the user to top up at cite42.dev/app/billing. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
485
|
+
inputSchema: import_zod.z.object({
|
|
486
|
+
trackerId: trackerIdInput,
|
|
487
|
+
maxCostMicroPerRun: import_zod.z.number().int().nonnegative().describe("Exact estimatedCostMicroPerRun returned by the latest tracker record.")
|
|
488
|
+
}),
|
|
489
|
+
execute: async ({ trackerId, maxCostMicroPerRun }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/activate`, { maxCostMicroPerRun })
|
|
490
|
+
});
|
|
491
|
+
var aivTrackerPause = (0, import_tools.createTool)({
|
|
492
|
+
id: "cite42_tracker_pause",
|
|
493
|
+
mcp: mutation("Pause Cite42 tracker", { idempotentHint: true }),
|
|
494
|
+
description: "Pause future recurring calls for a tracker while preserving its configuration and stored run history. Free: costs 0 credits.",
|
|
495
|
+
inputSchema: import_zod.z.object({ trackerId: trackerIdInput }),
|
|
496
|
+
execute: async ({ trackerId }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/pause`, {})
|
|
497
|
+
});
|
|
498
|
+
var aivTrackerDelete = (0, import_tools.createTool)({
|
|
499
|
+
id: "cite42_tracker_delete",
|
|
500
|
+
mcp: mutation("Permanently delete Cite42 tracker", {
|
|
501
|
+
destructiveHint: true,
|
|
502
|
+
idempotentHint: true
|
|
503
|
+
}),
|
|
504
|
+
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 credit ledger records are retained. Free: costs 0 credits.",
|
|
505
|
+
inputSchema: import_zod.z.object({ trackerId: trackerIdInput }),
|
|
506
|
+
execute: async ({ trackerId }) => aivDelete(`/trackers/${encodeURIComponent(trackerId)}`)
|
|
507
|
+
});
|
|
508
|
+
var aivTrackerRun = (0, import_tools.createTool)({
|
|
509
|
+
id: "cite42_tracker_run",
|
|
510
|
+
mcp: mutation("Run Cite42 tracker now", {
|
|
511
|
+
openWorldHint: true,
|
|
512
|
+
idempotentHint: true
|
|
513
|
+
}),
|
|
514
|
+
description: `Queue one manual run using the tracker\u2019s saved primitive inputs. A repeated requestToken returns the same logical run. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
515
|
+
inputSchema: import_zod.z.object({
|
|
516
|
+
trackerId: trackerIdInput,
|
|
517
|
+
requestToken: import_zod.z.string().trim().min(1).max(200).describe("Stable caller-generated token used to deduplicate a retried manual run.")
|
|
518
|
+
}),
|
|
519
|
+
execute: async ({ trackerId, requestToken }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/runs`, { requestToken })
|
|
520
|
+
});
|
|
521
|
+
var aivTrackers = (0, import_tools.createTool)({
|
|
522
|
+
id: "cite42_trackers",
|
|
523
|
+
mcp: readOnly("List Cite42 trackers", false),
|
|
524
|
+
description: "List the connected account\u2019s trackers with status, cadence, primitive configuration, cost estimate, accepted per-run maximum, and next/last run times. Free: costs 0 credits.",
|
|
525
|
+
inputSchema: import_zod.z.object({}),
|
|
526
|
+
execute: async () => aivGet("/trackers")
|
|
527
|
+
});
|
|
528
|
+
var aivTrackerReport = (0, import_tools.createTool)({
|
|
529
|
+
id: "cite42_tracker_report",
|
|
530
|
+
mcp: readOnly("Read Cite42 tracker report", false),
|
|
531
|
+
description: "Read bounded stored run history for one tracker, including per-primitive results, changes from the prior comparable observation, statuses, sampled times, and costs. Free: costs 0 credits.",
|
|
532
|
+
inputSchema: import_zod.z.object({
|
|
533
|
+
trackerId: trackerIdInput,
|
|
534
|
+
limit: import_zod.z.number().int().min(1).max(20).optional().default(8).describe("Most recent runs to return, capped for LLM context.")
|
|
535
|
+
}),
|
|
536
|
+
execute: async ({ trackerId, limit }) => aivGet(`/trackers/${encodeURIComponent(trackerId)}/report?limit=${limit}`)
|
|
537
|
+
});
|
|
384
538
|
|
|
385
539
|
// src/server.ts
|
|
386
540
|
var CITE42_TOOLS = {
|
|
@@ -401,7 +555,15 @@ var CITE42_TOOLS = {
|
|
|
401
555
|
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
402
556
|
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
403
557
|
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
404
|
-
cite42_generate_content_brief: aivGenerateContentBrief
|
|
558
|
+
cite42_generate_content_brief: aivGenerateContentBrief,
|
|
559
|
+
cite42_tracker_create: aivTrackerCreate,
|
|
560
|
+
cite42_tracker_update: aivTrackerUpdate,
|
|
561
|
+
cite42_tracker_activate: aivTrackerActivate,
|
|
562
|
+
cite42_tracker_pause: aivTrackerPause,
|
|
563
|
+
cite42_tracker_delete: aivTrackerDelete,
|
|
564
|
+
cite42_tracker_run: aivTrackerRun,
|
|
565
|
+
cite42_trackers: aivTrackers,
|
|
566
|
+
cite42_tracker_report: aivTrackerReport
|
|
405
567
|
};
|
|
406
568
|
function buildCite42Server(version) {
|
|
407
569
|
return new import_mcp.MCPServer({
|
|
@@ -420,7 +582,7 @@ if (!resolveApiKey()) {
|
|
|
420
582
|
);
|
|
421
583
|
process.exit(1);
|
|
422
584
|
}
|
|
423
|
-
buildCite42Server("0.
|
|
585
|
+
buildCite42Server("0.3.0").startStdio().catch((error) => {
|
|
424
586
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
425
587
|
process.exit(1);
|
|
426
588
|
});
|
package/dist/stdio.mjs
CHANGED
|
@@ -86,9 +86,27 @@ function getBaseUrl() {
|
|
|
86
86
|
return readFlag("api-base") ?? process.env.CITE42_API_BASE ?? DEFAULT_BASE_URL;
|
|
87
87
|
}
|
|
88
88
|
async function aivPost(path, body) {
|
|
89
|
+
return aivWrite("POST", path, body);
|
|
90
|
+
}
|
|
91
|
+
async function aivPatch(path, body) {
|
|
92
|
+
return aivWrite("PATCH", path, body);
|
|
93
|
+
}
|
|
94
|
+
async function aivDelete(path) {
|
|
95
|
+
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
96
|
+
const res = await fetch(url, {
|
|
97
|
+
method: "DELETE",
|
|
98
|
+
headers: { Authorization: `Bearer ${getApiKey()}` }
|
|
99
|
+
});
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
const body = await res.text();
|
|
102
|
+
throw new Error(errorMessageForMcp(res.status, path, body));
|
|
103
|
+
}
|
|
104
|
+
return toCite42McpResult(await res.json());
|
|
105
|
+
}
|
|
106
|
+
async function aivWrite(method, path, body) {
|
|
89
107
|
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
90
108
|
const res = await fetch(url, {
|
|
91
|
-
method
|
|
109
|
+
method,
|
|
92
110
|
headers: {
|
|
93
111
|
"Content-Type": "application/json",
|
|
94
112
|
Authorization: `Bearer ${getApiKey()}`
|
|
@@ -124,9 +142,10 @@ var modelEnum = z.enum(["chatgpt", "claude", "perplexity", "gemini", "google_ai_
|
|
|
124
142
|
var QUERY_MAX_CHARS = 2e3;
|
|
125
143
|
var SHORT_TEXT_MAX_CHARS = 200;
|
|
126
144
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
127
|
-
var MODEL_SELECTION_DESCRIPTION = 'Optional model subset. Omit to query
|
|
145
|
+
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.';
|
|
146
|
+
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.";
|
|
128
147
|
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 500 Cite42 credits, estimate the credits and ask the user before continuing. When a result contains cite42Credits, that figure is Cite42 credits, not a model or host balance. Free (0 credits): cite42_credits for the remaining balance, cite42_pricing for exact per-tool prices and cost estimates, cite42_usage for recent calls.";
|
|
129
|
-
var MODEL_PRICING = "Pricing is per selected AI surface
|
|
148
|
+
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 for more credits. Use the models field when the user names a surface or asks to reduce credit use, and call cite42_pricing for exact current rates.";
|
|
130
149
|
var DATA_PRICING = "Data tools have flat per-call prices; call cite42_pricing for exact current rates.";
|
|
131
150
|
var WORKFLOW_PRICING = "Pricing: workflow cost is calculated from the primitive endpoints that run successfully and the selected model rates.";
|
|
132
151
|
function workflowDescription(description) {
|
|
@@ -135,6 +154,17 @@ function workflowDescription(description) {
|
|
|
135
154
|
function readOnly(title, openWorldHint = true) {
|
|
136
155
|
return { annotations: { title, readOnlyHint: true, openWorldHint } };
|
|
137
156
|
}
|
|
157
|
+
function mutation(title, options = {}) {
|
|
158
|
+
return {
|
|
159
|
+
annotations: {
|
|
160
|
+
title,
|
|
161
|
+
readOnlyHint: false,
|
|
162
|
+
destructiveHint: options.destructiveHint ?? false,
|
|
163
|
+
openWorldHint: options.openWorldHint ?? false,
|
|
164
|
+
...options.idempotentHint === void 0 ? {} : { idempotentHint: options.idempotentHint }
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
138
168
|
function queryText(description) {
|
|
139
169
|
return z.string().trim().min(1).max(QUERY_MAX_CHARS).describe(description);
|
|
140
170
|
}
|
|
@@ -145,6 +175,7 @@ function optionalShortText(description) {
|
|
|
145
175
|
return z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).optional().describe(description);
|
|
146
176
|
}
|
|
147
177
|
var modelsInput = z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
178
|
+
var fullSweepInput = z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
|
|
148
179
|
var competitorList = z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
149
180
|
var aivCredits = createTool({
|
|
150
181
|
id: "cite42_credits",
|
|
@@ -195,7 +226,8 @@ var aivSearch = createTool({
|
|
|
195
226
|
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} ${MCP_USAGE_GUIDANCE}`,
|
|
196
227
|
inputSchema: z.object({
|
|
197
228
|
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
198
|
-
models: modelsInput
|
|
229
|
+
models: modelsInput,
|
|
230
|
+
fullSweep: fullSweepInput
|
|
199
231
|
}),
|
|
200
232
|
execute: async (input) => aivPost("/search", input)
|
|
201
233
|
});
|
|
@@ -206,7 +238,8 @@ var aivCitations = createTool({
|
|
|
206
238
|
inputSchema: z.object({
|
|
207
239
|
query: queryText("The concise query to look up cited sources for."),
|
|
208
240
|
url: z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
|
|
209
|
-
models: modelsInput
|
|
241
|
+
models: modelsInput,
|
|
242
|
+
fullSweep: fullSweepInput
|
|
210
243
|
}),
|
|
211
244
|
execute: async (input) => aivPost("/citations", input)
|
|
212
245
|
});
|
|
@@ -217,7 +250,8 @@ var aivRankings = createTool({
|
|
|
217
250
|
inputSchema: z.object({
|
|
218
251
|
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
219
252
|
competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
|
|
220
|
-
models: modelsInput
|
|
253
|
+
models: modelsInput,
|
|
254
|
+
fullSweep: fullSweepInput
|
|
221
255
|
}),
|
|
222
256
|
execute: async (input) => aivPost("/rankings", input)
|
|
223
257
|
});
|
|
@@ -229,7 +263,8 @@ var aivCompare = createTool({
|
|
|
229
263
|
query: queryText("The concise query to compare the brand and competitors on."),
|
|
230
264
|
brand: shortText("Your brand, ranked alongside the competitors."),
|
|
231
265
|
competitors: z.array(shortText("Competitor brand/domain name.")).nonempty().max(10).describe("Competitor brand/domain names (up to 10) to rank against your brand."),
|
|
232
|
-
models: modelsInput
|
|
266
|
+
models: modelsInput,
|
|
267
|
+
fullSweep: fullSweepInput
|
|
233
268
|
}),
|
|
234
269
|
execute: async (input) => aivPost("/compare", input)
|
|
235
270
|
});
|
|
@@ -240,7 +275,8 @@ var aivSentiment = createTool({
|
|
|
240
275
|
inputSchema: z.object({
|
|
241
276
|
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
242
277
|
brand: shortText("The brand to score sentiment for in each model answer."),
|
|
243
|
-
models: modelsInput
|
|
278
|
+
models: modelsInput,
|
|
279
|
+
fullSweep: fullSweepInput
|
|
244
280
|
}),
|
|
245
281
|
execute: async (input) => aivPost("/sentiment", input)
|
|
246
282
|
});
|
|
@@ -324,7 +360,7 @@ var aivMapAiPrompts = createTool({
|
|
|
324
360
|
id: "cite42_map_ai_prompts",
|
|
325
361
|
mcp: readOnly("Map buyer prompts for AI search"),
|
|
326
362
|
description: workflowDescription(
|
|
327
|
-
"Run the AI-prompt-map workflow: discover and cluster buyer prompts worth tracking from AI search, SEO demand, trends, citations, Reddit, and YouTube data."
|
|
363
|
+
"Run the AI-prompt-map workflow: discover and cluster buyer prompts worth tracking from AI search, SEO demand, trends, citations, Reddit, and YouTube data. The prompts it returns are exactly what cite42_tracker_create takes, so offer to start tracking the best of them once the user has seen the map."
|
|
328
364
|
),
|
|
329
365
|
inputSchema: z.object({
|
|
330
366
|
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
@@ -361,7 +397,8 @@ var aivFindAiCitationGaps = createTool({
|
|
|
361
397
|
brand: shortText("Brand or domain that should earn citations."),
|
|
362
398
|
url: z.string().trim().max(500).optional().describe("Optional exact URL to check for citations."),
|
|
363
399
|
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
364
|
-
models: modelsInput
|
|
400
|
+
models: modelsInput,
|
|
401
|
+
fullSweep: fullSweepInput
|
|
365
402
|
}),
|
|
366
403
|
execute: async (input) => aivPost("/workflows/ai-citation-gaps", input)
|
|
367
404
|
});
|
|
@@ -380,6 +417,123 @@ var aivGenerateContentBrief = createTool({
|
|
|
380
417
|
}),
|
|
381
418
|
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
382
419
|
});
|
|
420
|
+
var trackerIdInput = z.string().trim().min(1).max(128).describe("Cite42 tracker ID returned by a tracker management or report tool.");
|
|
421
|
+
var trackerCadenceInput = z.object({
|
|
422
|
+
frequency: z.enum(["daily", "weekly"]),
|
|
423
|
+
timezone: z.string().trim().min(1).max(100).refine((timezone) => {
|
|
424
|
+
try {
|
|
425
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
426
|
+
return true;
|
|
427
|
+
} catch {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}, "Timezone must be a valid IANA timezone.").describe("IANA timezone, for example Europe/London or America/New_York."),
|
|
431
|
+
time: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/).describe("Local run time in 24-hour HH:mm format."),
|
|
432
|
+
weekday: z.number().int().min(0).max(6).optional().describe("Weekly day, where 0 is Sunday and 6 is Saturday.")
|
|
433
|
+
}).superRefine((cadence, context) => {
|
|
434
|
+
if (cadence.frequency === "weekly" && cadence.weekday === void 0) {
|
|
435
|
+
context.addIssue({
|
|
436
|
+
code: "custom",
|
|
437
|
+
path: ["weekday"],
|
|
438
|
+
message: "weekday is required for a weekly tracker"
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
var trackerPrimitiveInput = z.enum(["rankings", "compare", "citations", "sentiment"]).describe(
|
|
443
|
+
"What to measure on every prompt: rankings (which brands get named), compare (the brand against named competitors), citations (which sources are cited), or sentiment (how one brand is described). One measurement per tracker."
|
|
444
|
+
);
|
|
445
|
+
var trackerPromptsInput = z.array(queryText("A prompt to run on every scheduled run.")).min(1).max(25).describe(
|
|
446
|
+
"Prompts to track, up to 25. Each is billed as its own call on every run. Keep the wording stable so runs stay comparable, avoid dates, and do not repeat a prompt."
|
|
447
|
+
);
|
|
448
|
+
var trackerDefinitionFields = {
|
|
449
|
+
name: z.string().trim().min(1).max(100).describe("Human-readable tracker name."),
|
|
450
|
+
cadence: trackerCadenceInput,
|
|
451
|
+
primitive: trackerPrimitiveInput,
|
|
452
|
+
prompts: trackerPromptsInput,
|
|
453
|
+
brand: z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).nullish().describe("Required for compare and sentiment. Not used by rankings or citations."),
|
|
454
|
+
competitors: z.array(shortText("Brand or domain name.")).max(20).nullish().describe(
|
|
455
|
+
"Required for rankings (the brands to track, including your own) and compare (the rivals to rank against, up to 10). Not used by citations or sentiment."
|
|
456
|
+
),
|
|
457
|
+
url: z.string().trim().min(1).max(500).nullish().describe("Citations only: a URL to report on as cited or not cited."),
|
|
458
|
+
models: z.array(modelEnum).nonempty().nullish().describe(
|
|
459
|
+
"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."
|
|
460
|
+
)
|
|
461
|
+
};
|
|
462
|
+
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.";
|
|
463
|
+
var aivTrackerCreate = createTool({
|
|
464
|
+
id: "cite42_tracker_create",
|
|
465
|
+
mcp: mutation("Create Cite42 tracker draft"),
|
|
466
|
+
description: "Create a draft daily or weekly 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. When the user has no prompt list yet, run cite42_map_ai_prompts first and track the prompts it recommends. Creation does not start recurring calls. " + TRACKER_BILLING_DESCRIPTION,
|
|
467
|
+
inputSchema: z.object(trackerDefinitionFields),
|
|
468
|
+
execute: async (input) => aivPost("/trackers", input)
|
|
469
|
+
});
|
|
470
|
+
var aivTrackerUpdate = createTool({
|
|
471
|
+
id: "cite42_tracker_update",
|
|
472
|
+
mcp: mutation("Update Cite42 tracker", { destructiveHint: true }),
|
|
473
|
+
description: `Replace a tracker definition: name, cadence, measurement, prompts and configuration. Send the complete definition, not a partial patch \u2014 a tracker read returns this exact shape, so read it, change what you need, and send it back. An edit returns the tracker to draft so its new recurring cost can be confirmed. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
474
|
+
inputSchema: z.object({ trackerId: trackerIdInput, ...trackerDefinitionFields }),
|
|
475
|
+
execute: async ({ trackerId, ...input }) => aivPatch(`/trackers/${encodeURIComponent(trackerId)}`, input)
|
|
476
|
+
});
|
|
477
|
+
var aivTrackerActivate = createTool({
|
|
478
|
+
id: "cite42_tracker_activate",
|
|
479
|
+
mcp: mutation("Activate Cite42 tracker", {
|
|
480
|
+
openWorldHint: true,
|
|
481
|
+
idempotentHint: true
|
|
482
|
+
}),
|
|
483
|
+
description: `Activate or resume recurring daily or weekly primitive calls. maxCostMicroPerRun must equal the tracker estimate being accepted; activation authorizes future billed calls until the tracker is paused. The response carries a billing block with the balance, the cost per run, and runsDryAt \u2014 the date the balance stops covering the account's active trackers \u2014 so relay it. Activation is refused when the balance cannot cover even the next run; tell the user to top up at cite42.dev/app/billing. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
484
|
+
inputSchema: z.object({
|
|
485
|
+
trackerId: trackerIdInput,
|
|
486
|
+
maxCostMicroPerRun: z.number().int().nonnegative().describe("Exact estimatedCostMicroPerRun returned by the latest tracker record.")
|
|
487
|
+
}),
|
|
488
|
+
execute: async ({ trackerId, maxCostMicroPerRun }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/activate`, { maxCostMicroPerRun })
|
|
489
|
+
});
|
|
490
|
+
var aivTrackerPause = createTool({
|
|
491
|
+
id: "cite42_tracker_pause",
|
|
492
|
+
mcp: mutation("Pause Cite42 tracker", { idempotentHint: true }),
|
|
493
|
+
description: "Pause future recurring calls for a tracker while preserving its configuration and stored run history. Free: costs 0 credits.",
|
|
494
|
+
inputSchema: z.object({ trackerId: trackerIdInput }),
|
|
495
|
+
execute: async ({ trackerId }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/pause`, {})
|
|
496
|
+
});
|
|
497
|
+
var aivTrackerDelete = createTool({
|
|
498
|
+
id: "cite42_tracker_delete",
|
|
499
|
+
mcp: mutation("Permanently delete Cite42 tracker", {
|
|
500
|
+
destructiveHint: true,
|
|
501
|
+
idempotentHint: true
|
|
502
|
+
}),
|
|
503
|
+
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 credit ledger records are retained. Free: costs 0 credits.",
|
|
504
|
+
inputSchema: z.object({ trackerId: trackerIdInput }),
|
|
505
|
+
execute: async ({ trackerId }) => aivDelete(`/trackers/${encodeURIComponent(trackerId)}`)
|
|
506
|
+
});
|
|
507
|
+
var aivTrackerRun = createTool({
|
|
508
|
+
id: "cite42_tracker_run",
|
|
509
|
+
mcp: mutation("Run Cite42 tracker now", {
|
|
510
|
+
openWorldHint: true,
|
|
511
|
+
idempotentHint: true
|
|
512
|
+
}),
|
|
513
|
+
description: `Queue one manual run using the tracker\u2019s saved primitive inputs. A repeated requestToken returns the same logical run. ${TRACKER_BILLING_DESCRIPTION}`,
|
|
514
|
+
inputSchema: z.object({
|
|
515
|
+
trackerId: trackerIdInput,
|
|
516
|
+
requestToken: z.string().trim().min(1).max(200).describe("Stable caller-generated token used to deduplicate a retried manual run.")
|
|
517
|
+
}),
|
|
518
|
+
execute: async ({ trackerId, requestToken }) => aivPost(`/trackers/${encodeURIComponent(trackerId)}/runs`, { requestToken })
|
|
519
|
+
});
|
|
520
|
+
var aivTrackers = createTool({
|
|
521
|
+
id: "cite42_trackers",
|
|
522
|
+
mcp: readOnly("List Cite42 trackers", false),
|
|
523
|
+
description: "List the connected account\u2019s trackers with status, cadence, primitive configuration, cost estimate, accepted per-run maximum, and next/last run times. Free: costs 0 credits.",
|
|
524
|
+
inputSchema: z.object({}),
|
|
525
|
+
execute: async () => aivGet("/trackers")
|
|
526
|
+
});
|
|
527
|
+
var aivTrackerReport = createTool({
|
|
528
|
+
id: "cite42_tracker_report",
|
|
529
|
+
mcp: readOnly("Read Cite42 tracker report", false),
|
|
530
|
+
description: "Read bounded stored run history for one tracker, including per-primitive results, changes from the prior comparable observation, statuses, sampled times, and costs. Free: costs 0 credits.",
|
|
531
|
+
inputSchema: z.object({
|
|
532
|
+
trackerId: trackerIdInput,
|
|
533
|
+
limit: z.number().int().min(1).max(20).optional().default(8).describe("Most recent runs to return, capped for LLM context.")
|
|
534
|
+
}),
|
|
535
|
+
execute: async ({ trackerId, limit }) => aivGet(`/trackers/${encodeURIComponent(trackerId)}/report?limit=${limit}`)
|
|
536
|
+
});
|
|
383
537
|
|
|
384
538
|
// src/server.ts
|
|
385
539
|
var CITE42_TOOLS = {
|
|
@@ -400,7 +554,15 @@ var CITE42_TOOLS = {
|
|
|
400
554
|
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
401
555
|
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
402
556
|
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
403
|
-
cite42_generate_content_brief: aivGenerateContentBrief
|
|
557
|
+
cite42_generate_content_brief: aivGenerateContentBrief,
|
|
558
|
+
cite42_tracker_create: aivTrackerCreate,
|
|
559
|
+
cite42_tracker_update: aivTrackerUpdate,
|
|
560
|
+
cite42_tracker_activate: aivTrackerActivate,
|
|
561
|
+
cite42_tracker_pause: aivTrackerPause,
|
|
562
|
+
cite42_tracker_delete: aivTrackerDelete,
|
|
563
|
+
cite42_tracker_run: aivTrackerRun,
|
|
564
|
+
cite42_trackers: aivTrackers,
|
|
565
|
+
cite42_tracker_report: aivTrackerReport
|
|
404
566
|
};
|
|
405
567
|
function buildCite42Server(version) {
|
|
406
568
|
return new MCPServer({
|
|
@@ -419,7 +581,7 @@ if (!resolveApiKey()) {
|
|
|
419
581
|
);
|
|
420
582
|
process.exit(1);
|
|
421
583
|
}
|
|
422
|
-
buildCite42Server("0.
|
|
584
|
+
buildCite42Server("0.3.0").startStdio().catch((error) => {
|
|
423
585
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
424
586
|
process.exit(1);
|
|
425
587
|
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cite42/mcp",
|
|
3
3
|
"mcpName": "dev.cite42/mcp",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "Cite42 MCP server: query ChatGPT, Claude, Perplexity, Gemini & Google AI Overviews for brand rankings, AI citations, SEO keyword data, and Reddit/YouTube trends. Works with Claude Code, Claude Desktop, Codex CLI, Cursor, and any MCP client.",
|
|
4
|
+
"version": "0.3.0",
|
|
5
|
+
"description": "Cite42 MCP server: query ChatGPT, Claude, Perplexity, Gemini & Google AI Overviews for brand rankings, AI citations, SEO keyword data, and Reddit/YouTube trends, and schedule daily or weekly 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",
|
|
8
8
|
"bin": {
|
|
@@ -60,7 +60,10 @@
|
|
|
60
60
|
"claude-code",
|
|
61
61
|
"cursor",
|
|
62
62
|
"reddit",
|
|
63
|
-
"youtube"
|
|
63
|
+
"youtube",
|
|
64
|
+
"ai-visibility-tracking",
|
|
65
|
+
"rank-tracking",
|
|
66
|
+
"scheduled-monitoring"
|
|
64
67
|
],
|
|
65
68
|
"engines": {
|
|
66
69
|
"node": ">=18"
|