@cite42/mcp 0.1.15 → 0.1.16
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 -18
- package/dist/stdio.cjs +147 -39
- package/dist/stdio.mjs +147 -39
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server for [Cite42, the AI search visibility, SEO keyword & trends API](https://www.cite42.dev): market research tools for your AI agent.
|
|
4
4
|
|
|
5
|
-
Once
|
|
5
|
+
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
|
|
@@ -14,14 +14,16 @@ Once installed, your MCP client (Claude Code, Claude Desktop, Codex CLI, Cursor,
|
|
|
14
14
|
|
|
15
15
|
## Requirements
|
|
16
16
|
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
17
|
+
- **A Cite42 account**, free to create at [cite42.dev](https://www.cite42.dev)
|
|
18
|
+
- **Node.js 18 or newer and a Cite42 API key** for Claude Code, Codex CLI, and Cursor
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Claude Desktop connects to Cite42 through the hosted remote MCP URL and OAuth. Claude Code, Codex CLI, and Cursor run this package on demand with `npx -y @cite42/mcp`.
|
|
23
23
|
|
|
24
|
-
### Step 1: Get your API key
|
|
24
|
+
### Step 1: Get your API key for local clients
|
|
25
|
+
|
|
26
|
+
Skip this step for Claude Desktop; its hosted connector authorizes through sign-in.
|
|
25
27
|
|
|
26
28
|
1. Sign in at [cite42.dev](https://www.cite42.dev)
|
|
27
29
|
2. Open **Dashboard → API Keys**: [www.cite42.dev/app/keys](https://www.cite42.dev/app/keys)
|
|
@@ -29,7 +31,7 @@ There is nothing to install manually. Every client below runs the server on dema
|
|
|
29
31
|
|
|
30
32
|
### Step 2: Add the server to your client
|
|
31
33
|
|
|
32
|
-
Pick **one** of the clients below and replace `cite42_live_your_key_here` with your real key.
|
|
34
|
+
Pick **one** of the clients below. For Claude Code, Codex CLI, and Cursor, replace `cite42_live_your_key_here` with your real key.
|
|
33
35
|
|
|
34
36
|
#### Claude Code
|
|
35
37
|
|
|
@@ -37,15 +39,29 @@ Pick **one** of the clients below and replace `cite42_live_your_key_here` with y
|
|
|
37
39
|
claude mcp add cite42 -e CITE42_API_KEY=cite42_live_your_key_here -- npx -y @cite42/mcp
|
|
38
40
|
```
|
|
39
41
|
|
|
42
|
+
#### Claude Desktop
|
|
43
|
+
|
|
44
|
+
1. Open **Settings → Connectors** and click **Add**.
|
|
45
|
+
2. Select **Add custom connector**.
|
|
46
|
+
3. Enter **Cite42** for the name and paste this remote MCP URL:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
https://mcp.cite42.dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
4. Click **Connect** and complete the Cite42 sign-in. No API key is required.
|
|
53
|
+
|
|
54
|
+
The same hosted-connector steps apply in Claude.ai.
|
|
55
|
+
|
|
40
56
|
#### Codex CLI
|
|
41
57
|
|
|
42
58
|
```bash
|
|
43
59
|
codex mcp add cite42 --env CITE42_API_KEY=cite42_live_your_key_here -- npx -y @cite42/mcp
|
|
44
60
|
```
|
|
45
61
|
|
|
46
|
-
####
|
|
62
|
+
#### Cursor
|
|
47
63
|
|
|
48
|
-
Add this
|
|
64
|
+
Add this under **Settings → MCP**, or save it to `~/.cursor/mcp.json`:
|
|
49
65
|
|
|
50
66
|
```json
|
|
51
67
|
{
|
|
@@ -61,30 +77,27 @@ Add this to `claude_desktop_config.json` (**Settings → Developer → Edit Conf
|
|
|
61
77
|
}
|
|
62
78
|
```
|
|
63
79
|
|
|
64
|
-
#### Cursor / Windsurf / other JSON-config clients
|
|
65
|
-
|
|
66
|
-
Use the same JSON block as Claude Desktop in your client's `mcpServers` config (Cursor: `~/.cursor/mcp.json` or **Settings → MCP**).
|
|
67
|
-
|
|
68
80
|
### Step 3: Restart so the server loads
|
|
69
81
|
|
|
70
|
-
MCP servers are started by the client, so
|
|
82
|
+
Local MCP servers are started by the client, so their config is only picked up on a fresh start. The Claude Desktop connector is ready after sign-in.
|
|
71
83
|
|
|
72
84
|
- **Claude Code / Codex CLI**: exit the current session and open a **new terminal** (or start a new `claude` / `codex` session)
|
|
73
|
-
- **
|
|
85
|
+
- **Cursor**: fully quit and reopen the app
|
|
74
86
|
|
|
75
|
-
You do not need to keep `npx -y @cite42/mcp` running in a separate terminal.
|
|
87
|
+
You do not need to keep `npx -y @cite42/mcp` running in a separate terminal. Local MCP clients start it automatically when the session needs tools.
|
|
76
88
|
|
|
77
89
|
### Step 4: Verify it's running
|
|
78
90
|
|
|
79
91
|
- **Claude Code:** run `claude mcp list` and check that `cite42` shows as ✔ connected. Or type `/mcp` inside a session.
|
|
92
|
+
- **Claude Desktop:** open **Settings → Connectors** and confirm Cite42 is connected.
|
|
80
93
|
- **Codex CLI:** run `codex mcp list`.
|
|
81
|
-
- **
|
|
94
|
+
- **Cursor:** look for `cite42` in the MCP/tools indicator. 15 tools should be listed.
|
|
82
95
|
|
|
83
96
|
If it doesn't appear, see [Troubleshooting](#troubleshooting).
|
|
84
97
|
|
|
85
98
|
## Try it: example prompts
|
|
86
99
|
|
|
87
|
-
After restarting, just ask in plain language. Your agent picks the right Cite42 tool automatically.
|
|
100
|
+
After connecting or restarting, just ask in plain language. Your agent picks the right Cite42 tool automatically.
|
|
88
101
|
|
|
89
102
|
- *"Use Cite42 to check which brands AI models recommend for 'best project management software'."*
|
|
90
103
|
- *"Which sources do AI models cite when asked about CRM tools for startups? Is mycompany.com among them?"*
|
|
@@ -123,7 +136,7 @@ The exact brands and sources change by query and model. A good result should sho
|
|
|
123
136
|
|
|
124
137
|
### SEO & trend data
|
|
125
138
|
|
|
126
|
-
- `cite42_keywords`: get search volume, CPC, competition,
|
|
139
|
+
- `cite42_keywords`: get seed search volume, CPC, and competition, with optional related keyword ideas.
|
|
127
140
|
- `cite42_trends`: get Google Trends interest over time, related and rising queries, and a trend label.
|
|
128
141
|
- `cite42_reddit_trends`: find Reddit audience questions, pain points, rising threads, and product mentions.
|
|
129
142
|
- `cite42_youtube_trends`: find YouTube rising videos, creator angles, title patterns, and opportunities.
|
package/dist/stdio.cjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
// src/stdio.ts
|
|
5
|
-
var import_mcp = require("@mastra/mcp");
|
|
6
|
-
|
|
7
4
|
// src/client.ts
|
|
8
5
|
var DEFAULT_BASE_URL = "https://www.cite42.dev/api/v1";
|
|
9
6
|
var MICRO_CREDITS_PER_CREDIT = 1e3;
|
|
@@ -40,6 +37,14 @@ function toCite42McpResult(value) {
|
|
|
40
37
|
}
|
|
41
38
|
};
|
|
42
39
|
}
|
|
40
|
+
function toCite42CreditsBalance(balanceMicroCredits) {
|
|
41
|
+
return {
|
|
42
|
+
cite42Credits: {
|
|
43
|
+
remaining: toCredits(balanceMicroCredits),
|
|
44
|
+
message: `Cite42 credits: ${formatCredits(balanceMicroCredits)} remaining. This balance check was free.`
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
43
48
|
function errorMessageForMcp(status, path, body) {
|
|
44
49
|
try {
|
|
45
50
|
const payload = JSON.parse(body);
|
|
@@ -60,6 +65,7 @@ function readFlag(name) {
|
|
|
60
65
|
const prefix = `--${name}=`;
|
|
61
66
|
for (let i = 0; i < argv.length; i++) {
|
|
62
67
|
const arg = argv[i];
|
|
68
|
+
if (!arg) continue;
|
|
63
69
|
if (arg.startsWith(prefix)) return arg.slice(prefix.length);
|
|
64
70
|
if (arg === `--${name}` && i + 1 < argv.length) return argv[i + 1];
|
|
65
71
|
}
|
|
@@ -96,6 +102,21 @@ async function aivPost(path, body) {
|
|
|
96
102
|
}
|
|
97
103
|
return toCite42McpResult(await res.json());
|
|
98
104
|
}
|
|
105
|
+
async function aivGet(path) {
|
|
106
|
+
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
107
|
+
const res = await fetch(url, {
|
|
108
|
+
method: "GET",
|
|
109
|
+
headers: { Authorization: `Bearer ${getApiKey()}` }
|
|
110
|
+
});
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
const text = await res.text();
|
|
113
|
+
throw new Error(errorMessageForMcp(res.status, path, text));
|
|
114
|
+
}
|
|
115
|
+
return toCite42McpResult(await res.json());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/server.ts
|
|
119
|
+
var import_mcp = require("@mastra/mcp");
|
|
99
120
|
|
|
100
121
|
// src/tools.ts
|
|
101
122
|
var import_tools = require("@mastra/core/tools");
|
|
@@ -105,9 +126,16 @@ var QUERY_MAX_CHARS = 2e3;
|
|
|
105
126
|
var SHORT_TEXT_MAX_CHARS = 200;
|
|
106
127
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
107
128
|
var MODEL_SELECTION_DESCRIPTION = 'Optional model subset. Omit to query all five AI surfaces (chatgpt, claude, perplexity, gemini, google_ai_overview). Pass ["perplexity"] or ["perplexity","claude"] when the user asks for specific surfaces or wants to reduce cost.';
|
|
108
|
-
var MCP_USAGE_GUIDANCE = "
|
|
109
|
-
var MODEL_PRICING = "Pricing
|
|
110
|
-
var DATA_PRICING = "
|
|
129
|
+
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; omitting models queries all five. 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
|
+
var DATA_PRICING = "Data tools have flat per-call prices; call cite42_pricing for exact current rates.";
|
|
132
|
+
var WORKFLOW_PRICING = "Pricing: workflow cost is calculated from the primitive endpoints that run successfully and the selected model rates.";
|
|
133
|
+
function workflowDescription(description) {
|
|
134
|
+
return `${description} ${MCP_USAGE_GUIDANCE} ${MODEL_PRICING} ${DATA_PRICING} ${WORKFLOW_PRICING}`;
|
|
135
|
+
}
|
|
136
|
+
function readOnly(title, openWorldHint = true) {
|
|
137
|
+
return { annotations: { title, readOnlyHint: true, openWorldHint } };
|
|
138
|
+
}
|
|
111
139
|
function queryText(description) {
|
|
112
140
|
return import_zod.z.string().trim().min(1).max(QUERY_MAX_CHARS).describe(description);
|
|
113
141
|
}
|
|
@@ -119,8 +147,52 @@ function optionalShortText(description) {
|
|
|
119
147
|
}
|
|
120
148
|
var modelsInput = import_zod.z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
121
149
|
var competitorList = import_zod.z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
150
|
+
var aivCredits = (0, import_tools.createTool)({
|
|
151
|
+
id: "cite42_credits",
|
|
152
|
+
mcp: readOnly("Check Cite42 credit balance", false),
|
|
153
|
+
description: "Check the remaining Cite42 credit balance for the connected account. Free: costs 0 credits. " + MCP_USAGE_GUIDANCE,
|
|
154
|
+
inputSchema: import_zod.z.object({}),
|
|
155
|
+
execute: async () => {
|
|
156
|
+
const usage = await aivGet("/usage");
|
|
157
|
+
const microCredits = usage.balance?.microCredits;
|
|
158
|
+
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
159
|
+
throw new Error("Cite42 usage response did not include a balance.");
|
|
160
|
+
}
|
|
161
|
+
return toCite42CreditsBalance(microCredits);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
var aivPricing = (0, import_tools.createTool)({
|
|
165
|
+
id: "cite42_pricing",
|
|
166
|
+
mcp: readOnly("Get Cite42 price table", false),
|
|
167
|
+
description: 'Get the exact Cite42 price table in credits: 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: costs 0 credits. ' + MCP_USAGE_GUIDANCE,
|
|
168
|
+
inputSchema: import_zod.z.object({}),
|
|
169
|
+
execute: async () => aivGet("/pricing")
|
|
170
|
+
});
|
|
171
|
+
var aivUsage = (0, import_tools.createTool)({
|
|
172
|
+
id: "cite42_usage",
|
|
173
|
+
mcp: readOnly("Get recent Cite42 usage", false),
|
|
174
|
+
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining credit balance. Free: costs 0 credits. " + MCP_USAGE_GUIDANCE,
|
|
175
|
+
inputSchema: import_zod.z.object({}),
|
|
176
|
+
execute: async () => {
|
|
177
|
+
const usage = await aivGet("/usage");
|
|
178
|
+
const microCredits = usage.balance?.microCredits;
|
|
179
|
+
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
180
|
+
throw new Error("Cite42 usage response did not include a balance.");
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
...toCite42CreditsBalance(microCredits),
|
|
184
|
+
recentCalls: (usage.recent ?? []).slice(0, 20).map((row) => ({
|
|
185
|
+
endpoint: row.endpoint,
|
|
186
|
+
status: row.status,
|
|
187
|
+
credits: typeof row.costMicro === "number" ? toCredits(row.costMicro) : void 0,
|
|
188
|
+
at: row.createdAt
|
|
189
|
+
}))
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
});
|
|
122
193
|
var aivSearch = (0, import_tools.createTool)({
|
|
123
194
|
id: "cite42_search",
|
|
195
|
+
mcp: readOnly("Search AI models"),
|
|
124
196
|
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}`,
|
|
125
197
|
inputSchema: import_zod.z.object({
|
|
126
198
|
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
@@ -130,6 +202,7 @@ var aivSearch = (0, import_tools.createTool)({
|
|
|
130
202
|
});
|
|
131
203
|
var aivCitations = (0, import_tools.createTool)({
|
|
132
204
|
id: "cite42_citations",
|
|
205
|
+
mcp: readOnly("Find AI citations"),
|
|
133
206
|
description: `Find which sources AI models cite for a query. Aggregates cited URLs across models with per-model counts; optionally checks whether a specific URL is cited. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
134
207
|
inputSchema: import_zod.z.object({
|
|
135
208
|
query: queryText("The concise query to look up cited sources for."),
|
|
@@ -140,6 +213,7 @@ var aivCitations = (0, import_tools.createTool)({
|
|
|
140
213
|
});
|
|
141
214
|
var aivRankings = (0, import_tools.createTool)({
|
|
142
215
|
id: "cite42_rankings",
|
|
216
|
+
mcp: readOnly("Measure AI brand rankings"),
|
|
143
217
|
description: `Measure how a set of brands/competitors rank in AI answers for a query: mention rate, average position, and a per-model breakdown across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
144
218
|
inputSchema: import_zod.z.object({
|
|
145
219
|
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
@@ -150,6 +224,7 @@ var aivRankings = (0, import_tools.createTool)({
|
|
|
150
224
|
});
|
|
151
225
|
var aivCompare = (0, import_tools.createTool)({
|
|
152
226
|
id: "cite42_compare",
|
|
227
|
+
mcp: readOnly("Compare brand against competitors"),
|
|
153
228
|
description: `Compare one primary brand head-to-head against a competitor set in AI answers: per-brand rankings plus a co-mention graph of which brands the models name together. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
154
229
|
inputSchema: import_zod.z.object({
|
|
155
230
|
query: queryText("The concise query to compare the brand and competitors on."),
|
|
@@ -161,6 +236,7 @@ var aivCompare = (0, import_tools.createTool)({
|
|
|
161
236
|
});
|
|
162
237
|
var aivSentiment = (0, import_tools.createTool)({
|
|
163
238
|
id: "cite42_sentiment",
|
|
239
|
+
mcp: readOnly("Score brand sentiment in AI answers"),
|
|
164
240
|
description: `Score how each AI model talks about a brand for a query: a positive/neutral/negative split per model with the verbatim phrases driving the tone, plus an aggregate across models. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
165
241
|
inputSchema: import_zod.z.object({
|
|
166
242
|
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
@@ -171,26 +247,31 @@ var aivSentiment = (0, import_tools.createTool)({
|
|
|
171
247
|
});
|
|
172
248
|
var aivKeywords = (0, import_tools.createTool)({
|
|
173
249
|
id: "cite42_keywords",
|
|
174
|
-
|
|
250
|
+
mcp: readOnly("Get SEO keyword data"),
|
|
251
|
+
description: `Classic keyword data for a seed term. Omit ideas when the user only needs that seed's monthly search volume, CPC, or competition. Set ideas:true only when the user asks for related, alternative, or long-tail suggestions from provider candidates. A flat-priced data tool, not a multi-model AI-search query. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
175
252
|
inputSchema: import_zod.z.object({
|
|
176
253
|
seed: queryText("Seed keyword or concise topic to fetch search volume, CPC, and competition for."),
|
|
177
254
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
178
|
-
ideas: import_zod.z.boolean().optional().describe(
|
|
255
|
+
ideas: import_zod.z.boolean().optional().describe(
|
|
256
|
+
"Omit for volume, CPC, and competition on the seed only. Set true only for related, alternative, or long-tail keyword ideas."
|
|
257
|
+
)
|
|
179
258
|
}),
|
|
180
259
|
execute: async (input) => aivPost("/keywords", input)
|
|
181
260
|
});
|
|
182
261
|
var aivTrends = (0, import_tools.createTool)({
|
|
183
262
|
id: "cite42_trends",
|
|
263
|
+
mcp: readOnly("Get Google Trends data"),
|
|
184
264
|
description: `Google Trends interest-over-time, related and rising queries, and a rising/falling/stable label for a term. A flat-priced data tool, not an AI-model query. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
185
265
|
inputSchema: import_zod.z.object({
|
|
186
266
|
term: queryText("Search term or concise topic to pull Google Trends interest-over-time for."),
|
|
187
267
|
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
|
|
188
|
-
geo: import_zod.z.string().trim().max(SHORT_TEXT_MAX_CHARS).optional().describe('Optional location name (e.g. "United
|
|
268
|
+
geo: import_zod.z.string().trim().max(SHORT_TEXT_MAX_CHARS).optional().describe('Optional location name (e.g. "United Kingdom"). Defaults to the United States when omitted.')
|
|
189
269
|
}),
|
|
190
270
|
execute: async (input) => aivPost("/trends", input)
|
|
191
271
|
});
|
|
192
272
|
var aivRedditTrends = (0, import_tools.createTool)({
|
|
193
273
|
id: "cite42_reddit_trends",
|
|
274
|
+
mcp: readOnly("Get Reddit trend data"),
|
|
194
275
|
description: `Reddit trend data for audience questions, pain points, rising threads, repeated phrases, mentioned products, and source URLs around a topic. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
195
276
|
inputSchema: import_zod.z.object({
|
|
196
277
|
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
@@ -201,6 +282,7 @@ var aivRedditTrends = (0, import_tools.createTool)({
|
|
|
201
282
|
});
|
|
202
283
|
var aivYoutubeTrends = (0, import_tools.createTool)({
|
|
203
284
|
id: "cite42_youtube_trends",
|
|
285
|
+
mcp: readOnly("Get YouTube trend data"),
|
|
204
286
|
description: `YouTube trend data for rising videos, creator angles, title patterns, channel examples, and video-market opportunities around a topic. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
205
287
|
inputSchema: import_zod.z.object({
|
|
206
288
|
topic: queryText("Topic, keyword, niche, or audience to research on YouTube."),
|
|
@@ -212,7 +294,10 @@ var aivYoutubeTrends = (0, import_tools.createTool)({
|
|
|
212
294
|
var channelEnum = import_zod.z.enum(["ai_search", "seo_keywords", "google_trends", "reddit", "youtube"]);
|
|
213
295
|
var aivFindContentOpportunities = (0, import_tools.createTool)({
|
|
214
296
|
id: "cite42_find_content_opportunities",
|
|
215
|
-
|
|
297
|
+
mcp: readOnly("Find content opportunities"),
|
|
298
|
+
description: workflowDescription(
|
|
299
|
+
"Run the content-opportunities workflow: fans out across AI search, SEO keywords, trends, Reddit, YouTube, and citations and returns the aggregated data in one response."
|
|
300
|
+
),
|
|
216
301
|
inputSchema: import_zod.z.object({
|
|
217
302
|
topic: queryText("Market, niche, product category, or audience to research."),
|
|
218
303
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
@@ -225,7 +310,10 @@ var aivFindContentOpportunities = (0, import_tools.createTool)({
|
|
|
225
310
|
});
|
|
226
311
|
var aivAnalyzeTopicDemand = (0, import_tools.createTool)({
|
|
227
312
|
id: "cite42_analyze_topic_demand",
|
|
228
|
-
|
|
313
|
+
mcp: readOnly("Analyze topic demand"),
|
|
314
|
+
description: workflowDescription(
|
|
315
|
+
"Run the topic-demand workflow: SEO keyword data, Google Trends, AI answers, and audience conversations, aggregated in one response."
|
|
316
|
+
),
|
|
229
317
|
inputSchema: import_zod.z.object({
|
|
230
318
|
topic: queryText("Topic, keyword, category, or audience demand to analyze."),
|
|
231
319
|
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
@@ -235,7 +323,10 @@ var aivAnalyzeTopicDemand = (0, import_tools.createTool)({
|
|
|
235
323
|
});
|
|
236
324
|
var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
237
325
|
id: "cite42_map_ai_prompts",
|
|
238
|
-
|
|
326
|
+
mcp: readOnly("Map buyer prompts for AI search"),
|
|
327
|
+
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."
|
|
329
|
+
),
|
|
239
330
|
inputSchema: import_zod.z.object({
|
|
240
331
|
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
241
332
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
@@ -248,7 +339,10 @@ var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
|
248
339
|
});
|
|
249
340
|
var aivAnalyzeCompetitorContent = (0, import_tools.createTool)({
|
|
250
341
|
id: "cite42_analyze_competitor_content",
|
|
251
|
-
|
|
342
|
+
mcp: readOnly("Analyze competitor content"),
|
|
343
|
+
description: workflowDescription(
|
|
344
|
+
"Run the competitor-content workflow: AI rankings, head-to-head comparison, citations, keyword demand, and topic coverage, aggregated in one response."
|
|
345
|
+
),
|
|
252
346
|
inputSchema: import_zod.z.object({
|
|
253
347
|
competitors: competitorList.describe("Competitor brands, domains, or channels to analyze."),
|
|
254
348
|
topic: import_zod.z.string().trim().min(1).max(QUERY_MAX_CHARS).optional().describe("Optional topic/category to constrain the analysis."),
|
|
@@ -259,7 +353,10 @@ var aivAnalyzeCompetitorContent = (0, import_tools.createTool)({
|
|
|
259
353
|
});
|
|
260
354
|
var aivFindAiCitationGaps = (0, import_tools.createTool)({
|
|
261
355
|
id: "cite42_find_ai_citation_gaps",
|
|
262
|
-
|
|
356
|
+
mcp: readOnly("Find AI citation gaps"),
|
|
357
|
+
description: workflowDescription(
|
|
358
|
+
"Run the AI-citation-gaps workflow: find queries where AI answers cite others but not your brand, domain, or URL, with the supporting data."
|
|
359
|
+
),
|
|
263
360
|
inputSchema: import_zod.z.object({
|
|
264
361
|
query: queryText("AI-search query to inspect for citation gaps."),
|
|
265
362
|
brand: shortText("Brand or domain that should earn citations."),
|
|
@@ -271,7 +368,10 @@ var aivFindAiCitationGaps = (0, import_tools.createTool)({
|
|
|
271
368
|
});
|
|
272
369
|
var aivGenerateContentBrief = (0, import_tools.createTool)({
|
|
273
370
|
id: "cite42_generate_content_brief",
|
|
274
|
-
|
|
371
|
+
mcp: readOnly("Generate content brief data"),
|
|
372
|
+
description: workflowDescription(
|
|
373
|
+
"Run the content-brief workflow: AI answers, citations, SEO keyword demand, trends, Reddit, and YouTube signals aggregated into brief-ready data."
|
|
374
|
+
),
|
|
275
375
|
inputSchema: import_zod.z.object({
|
|
276
376
|
topic: queryText("Topic, keyword, or content idea to brief."),
|
|
277
377
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
@@ -282,6 +382,37 @@ var aivGenerateContentBrief = (0, import_tools.createTool)({
|
|
|
282
382
|
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
283
383
|
});
|
|
284
384
|
|
|
385
|
+
// src/server.ts
|
|
386
|
+
var CITE42_TOOLS = {
|
|
387
|
+
cite42_credits: aivCredits,
|
|
388
|
+
cite42_pricing: aivPricing,
|
|
389
|
+
cite42_usage: aivUsage,
|
|
390
|
+
cite42_search: aivSearch,
|
|
391
|
+
cite42_rankings: aivRankings,
|
|
392
|
+
cite42_compare: aivCompare,
|
|
393
|
+
cite42_citations: aivCitations,
|
|
394
|
+
cite42_sentiment: aivSentiment,
|
|
395
|
+
cite42_keywords: aivKeywords,
|
|
396
|
+
cite42_trends: aivTrends,
|
|
397
|
+
cite42_reddit_trends: aivRedditTrends,
|
|
398
|
+
cite42_youtube_trends: aivYoutubeTrends,
|
|
399
|
+
cite42_find_content_opportunities: aivFindContentOpportunities,
|
|
400
|
+
cite42_analyze_topic_demand: aivAnalyzeTopicDemand,
|
|
401
|
+
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
402
|
+
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
403
|
+
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
404
|
+
cite42_generate_content_brief: aivGenerateContentBrief
|
|
405
|
+
};
|
|
406
|
+
function buildCite42Server(version) {
|
|
407
|
+
return new import_mcp.MCPServer({
|
|
408
|
+
id: "cite42",
|
|
409
|
+
name: "Cite42",
|
|
410
|
+
version,
|
|
411
|
+
description: "AI market data API: AI search, citations, SEO keywords, trends, and workflows.",
|
|
412
|
+
tools: CITE42_TOOLS
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
285
416
|
// src/stdio.ts
|
|
286
417
|
if (!resolveApiKey()) {
|
|
287
418
|
console.error(
|
|
@@ -289,30 +420,7 @@ if (!resolveApiKey()) {
|
|
|
289
420
|
);
|
|
290
421
|
process.exit(1);
|
|
291
422
|
}
|
|
292
|
-
|
|
293
|
-
id: "cite42",
|
|
294
|
-
name: "Cite42",
|
|
295
|
-
version: "0.1.15",
|
|
296
|
-
description: "AI market data API: AI search, citations, SEO keywords, trends, and workflows.",
|
|
297
|
-
tools: {
|
|
298
|
-
cite42_search: aivSearch,
|
|
299
|
-
cite42_rankings: aivRankings,
|
|
300
|
-
cite42_compare: aivCompare,
|
|
301
|
-
cite42_citations: aivCitations,
|
|
302
|
-
cite42_sentiment: aivSentiment,
|
|
303
|
-
cite42_keywords: aivKeywords,
|
|
304
|
-
cite42_trends: aivTrends,
|
|
305
|
-
cite42_reddit_trends: aivRedditTrends,
|
|
306
|
-
cite42_youtube_trends: aivYoutubeTrends,
|
|
307
|
-
cite42_find_content_opportunities: aivFindContentOpportunities,
|
|
308
|
-
cite42_analyze_topic_demand: aivAnalyzeTopicDemand,
|
|
309
|
-
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
310
|
-
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
311
|
-
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
312
|
-
cite42_generate_content_brief: aivGenerateContentBrief
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
server.startStdio().catch((error) => {
|
|
423
|
+
buildCite42Server("0.1.16").startStdio().catch((error) => {
|
|
316
424
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
317
425
|
process.exit(1);
|
|
318
426
|
});
|
package/dist/stdio.mjs
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/stdio.ts
|
|
4
|
-
import { MCPServer } from "@mastra/mcp";
|
|
5
|
-
|
|
6
3
|
// src/client.ts
|
|
7
4
|
var DEFAULT_BASE_URL = "https://www.cite42.dev/api/v1";
|
|
8
5
|
var MICRO_CREDITS_PER_CREDIT = 1e3;
|
|
@@ -39,6 +36,14 @@ function toCite42McpResult(value) {
|
|
|
39
36
|
}
|
|
40
37
|
};
|
|
41
38
|
}
|
|
39
|
+
function toCite42CreditsBalance(balanceMicroCredits) {
|
|
40
|
+
return {
|
|
41
|
+
cite42Credits: {
|
|
42
|
+
remaining: toCredits(balanceMicroCredits),
|
|
43
|
+
message: `Cite42 credits: ${formatCredits(balanceMicroCredits)} remaining. This balance check was free.`
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
42
47
|
function errorMessageForMcp(status, path, body) {
|
|
43
48
|
try {
|
|
44
49
|
const payload = JSON.parse(body);
|
|
@@ -59,6 +64,7 @@ function readFlag(name) {
|
|
|
59
64
|
const prefix = `--${name}=`;
|
|
60
65
|
for (let i = 0; i < argv.length; i++) {
|
|
61
66
|
const arg = argv[i];
|
|
67
|
+
if (!arg) continue;
|
|
62
68
|
if (arg.startsWith(prefix)) return arg.slice(prefix.length);
|
|
63
69
|
if (arg === `--${name}` && i + 1 < argv.length) return argv[i + 1];
|
|
64
70
|
}
|
|
@@ -95,6 +101,21 @@ async function aivPost(path, body) {
|
|
|
95
101
|
}
|
|
96
102
|
return toCite42McpResult(await res.json());
|
|
97
103
|
}
|
|
104
|
+
async function aivGet(path) {
|
|
105
|
+
const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
106
|
+
const res = await fetch(url, {
|
|
107
|
+
method: "GET",
|
|
108
|
+
headers: { Authorization: `Bearer ${getApiKey()}` }
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
const text = await res.text();
|
|
112
|
+
throw new Error(errorMessageForMcp(res.status, path, text));
|
|
113
|
+
}
|
|
114
|
+
return toCite42McpResult(await res.json());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/server.ts
|
|
118
|
+
import { MCPServer } from "@mastra/mcp";
|
|
98
119
|
|
|
99
120
|
// src/tools.ts
|
|
100
121
|
import { createTool } from "@mastra/core/tools";
|
|
@@ -104,9 +125,16 @@ var QUERY_MAX_CHARS = 2e3;
|
|
|
104
125
|
var SHORT_TEXT_MAX_CHARS = 200;
|
|
105
126
|
var TIMEFRAME_MAX_CHARS = 80;
|
|
106
127
|
var MODEL_SELECTION_DESCRIPTION = 'Optional model subset. Omit to query all five AI surfaces (chatgpt, claude, perplexity, gemini, google_ai_overview). Pass ["perplexity"] or ["perplexity","claude"] when the user asks for specific surfaces or wants to reduce cost.';
|
|
107
|
-
var MCP_USAGE_GUIDANCE = "
|
|
108
|
-
var MODEL_PRICING = "Pricing
|
|
109
|
-
var DATA_PRICING = "
|
|
128
|
+
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; omitting models queries all five. 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
|
+
var DATA_PRICING = "Data tools have flat per-call prices; call cite42_pricing for exact current rates.";
|
|
131
|
+
var WORKFLOW_PRICING = "Pricing: workflow cost is calculated from the primitive endpoints that run successfully and the selected model rates.";
|
|
132
|
+
function workflowDescription(description) {
|
|
133
|
+
return `${description} ${MCP_USAGE_GUIDANCE} ${MODEL_PRICING} ${DATA_PRICING} ${WORKFLOW_PRICING}`;
|
|
134
|
+
}
|
|
135
|
+
function readOnly(title, openWorldHint = true) {
|
|
136
|
+
return { annotations: { title, readOnlyHint: true, openWorldHint } };
|
|
137
|
+
}
|
|
110
138
|
function queryText(description) {
|
|
111
139
|
return z.string().trim().min(1).max(QUERY_MAX_CHARS).describe(description);
|
|
112
140
|
}
|
|
@@ -118,8 +146,52 @@ function optionalShortText(description) {
|
|
|
118
146
|
}
|
|
119
147
|
var modelsInput = z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
120
148
|
var competitorList = z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
149
|
+
var aivCredits = createTool({
|
|
150
|
+
id: "cite42_credits",
|
|
151
|
+
mcp: readOnly("Check Cite42 credit balance", false),
|
|
152
|
+
description: "Check the remaining Cite42 credit balance for the connected account. Free: costs 0 credits. " + MCP_USAGE_GUIDANCE,
|
|
153
|
+
inputSchema: z.object({}),
|
|
154
|
+
execute: async () => {
|
|
155
|
+
const usage = await aivGet("/usage");
|
|
156
|
+
const microCredits = usage.balance?.microCredits;
|
|
157
|
+
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
158
|
+
throw new Error("Cite42 usage response did not include a balance.");
|
|
159
|
+
}
|
|
160
|
+
return toCite42CreditsBalance(microCredits);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
var aivPricing = createTool({
|
|
164
|
+
id: "cite42_pricing",
|
|
165
|
+
mcp: readOnly("Get Cite42 price table", false),
|
|
166
|
+
description: 'Get the exact Cite42 price table in credits: 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: costs 0 credits. ' + MCP_USAGE_GUIDANCE,
|
|
167
|
+
inputSchema: z.object({}),
|
|
168
|
+
execute: async () => aivGet("/pricing")
|
|
169
|
+
});
|
|
170
|
+
var aivUsage = createTool({
|
|
171
|
+
id: "cite42_usage",
|
|
172
|
+
mcp: readOnly("Get recent Cite42 usage", false),
|
|
173
|
+
description: "Get recent Cite42 usage for the connected account: the last calls with their cost and status, plus the remaining credit balance. Free: costs 0 credits. " + MCP_USAGE_GUIDANCE,
|
|
174
|
+
inputSchema: z.object({}),
|
|
175
|
+
execute: async () => {
|
|
176
|
+
const usage = await aivGet("/usage");
|
|
177
|
+
const microCredits = usage.balance?.microCredits;
|
|
178
|
+
if (typeof microCredits !== "number" || !Number.isFinite(microCredits)) {
|
|
179
|
+
throw new Error("Cite42 usage response did not include a balance.");
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
...toCite42CreditsBalance(microCredits),
|
|
183
|
+
recentCalls: (usage.recent ?? []).slice(0, 20).map((row) => ({
|
|
184
|
+
endpoint: row.endpoint,
|
|
185
|
+
status: row.status,
|
|
186
|
+
credits: typeof row.costMicro === "number" ? toCredits(row.costMicro) : void 0,
|
|
187
|
+
at: row.createdAt
|
|
188
|
+
}))
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
});
|
|
121
192
|
var aivSearch = createTool({
|
|
122
193
|
id: "cite42_search",
|
|
194
|
+
mcp: readOnly("Search AI models"),
|
|
123
195
|
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}`,
|
|
124
196
|
inputSchema: z.object({
|
|
125
197
|
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
@@ -129,6 +201,7 @@ var aivSearch = createTool({
|
|
|
129
201
|
});
|
|
130
202
|
var aivCitations = createTool({
|
|
131
203
|
id: "cite42_citations",
|
|
204
|
+
mcp: readOnly("Find AI citations"),
|
|
132
205
|
description: `Find which sources AI models cite for a query. Aggregates cited URLs across models with per-model counts; optionally checks whether a specific URL is cited. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
133
206
|
inputSchema: z.object({
|
|
134
207
|
query: queryText("The concise query to look up cited sources for."),
|
|
@@ -139,6 +212,7 @@ var aivCitations = createTool({
|
|
|
139
212
|
});
|
|
140
213
|
var aivRankings = createTool({
|
|
141
214
|
id: "cite42_rankings",
|
|
215
|
+
mcp: readOnly("Measure AI brand rankings"),
|
|
142
216
|
description: `Measure how a set of brands/competitors rank in AI answers for a query: mention rate, average position, and a per-model breakdown across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
143
217
|
inputSchema: z.object({
|
|
144
218
|
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
@@ -149,6 +223,7 @@ var aivRankings = createTool({
|
|
|
149
223
|
});
|
|
150
224
|
var aivCompare = createTool({
|
|
151
225
|
id: "cite42_compare",
|
|
226
|
+
mcp: readOnly("Compare brand against competitors"),
|
|
152
227
|
description: `Compare one primary brand head-to-head against a competitor set in AI answers: per-brand rankings plus a co-mention graph of which brands the models name together. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
153
228
|
inputSchema: z.object({
|
|
154
229
|
query: queryText("The concise query to compare the brand and competitors on."),
|
|
@@ -160,6 +235,7 @@ var aivCompare = createTool({
|
|
|
160
235
|
});
|
|
161
236
|
var aivSentiment = createTool({
|
|
162
237
|
id: "cite42_sentiment",
|
|
238
|
+
mcp: readOnly("Score brand sentiment in AI answers"),
|
|
163
239
|
description: `Score how each AI model talks about a brand for a query: a positive/neutral/negative split per model with the verbatim phrases driving the tone, plus an aggregate across models. ${MODEL_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
164
240
|
inputSchema: z.object({
|
|
165
241
|
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
@@ -170,26 +246,31 @@ var aivSentiment = createTool({
|
|
|
170
246
|
});
|
|
171
247
|
var aivKeywords = createTool({
|
|
172
248
|
id: "cite42_keywords",
|
|
173
|
-
|
|
249
|
+
mcp: readOnly("Get SEO keyword data"),
|
|
250
|
+
description: `Classic keyword data for a seed term. Omit ideas when the user only needs that seed's monthly search volume, CPC, or competition. Set ideas:true only when the user asks for related, alternative, or long-tail suggestions from provider candidates. A flat-priced data tool, not a multi-model AI-search query. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
174
251
|
inputSchema: z.object({
|
|
175
252
|
seed: queryText("Seed keyword or concise topic to fetch search volume, CPC, and competition for."),
|
|
176
253
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
177
|
-
ideas: z.boolean().optional().describe(
|
|
254
|
+
ideas: z.boolean().optional().describe(
|
|
255
|
+
"Omit for volume, CPC, and competition on the seed only. Set true only for related, alternative, or long-tail keyword ideas."
|
|
256
|
+
)
|
|
178
257
|
}),
|
|
179
258
|
execute: async (input) => aivPost("/keywords", input)
|
|
180
259
|
});
|
|
181
260
|
var aivTrends = createTool({
|
|
182
261
|
id: "cite42_trends",
|
|
262
|
+
mcp: readOnly("Get Google Trends data"),
|
|
183
263
|
description: `Google Trends interest-over-time, related and rising queries, and a rising/falling/stable label for a term. A flat-priced data tool, not an AI-model query. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
184
264
|
inputSchema: z.object({
|
|
185
265
|
term: queryText("Search term or concise topic to pull Google Trends interest-over-time for."),
|
|
186
266
|
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
|
|
187
|
-
geo: z.string().trim().max(SHORT_TEXT_MAX_CHARS).optional().describe('Optional location name (e.g. "United
|
|
267
|
+
geo: z.string().trim().max(SHORT_TEXT_MAX_CHARS).optional().describe('Optional location name (e.g. "United Kingdom"). Defaults to the United States when omitted.')
|
|
188
268
|
}),
|
|
189
269
|
execute: async (input) => aivPost("/trends", input)
|
|
190
270
|
});
|
|
191
271
|
var aivRedditTrends = createTool({
|
|
192
272
|
id: "cite42_reddit_trends",
|
|
273
|
+
mcp: readOnly("Get Reddit trend data"),
|
|
193
274
|
description: `Reddit trend data for audience questions, pain points, rising threads, repeated phrases, mentioned products, and source URLs around a topic. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
194
275
|
inputSchema: z.object({
|
|
195
276
|
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
@@ -200,6 +281,7 @@ var aivRedditTrends = createTool({
|
|
|
200
281
|
});
|
|
201
282
|
var aivYoutubeTrends = createTool({
|
|
202
283
|
id: "cite42_youtube_trends",
|
|
284
|
+
mcp: readOnly("Get YouTube trend data"),
|
|
203
285
|
description: `YouTube trend data for rising videos, creator angles, title patterns, channel examples, and video-market opportunities around a topic. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
204
286
|
inputSchema: z.object({
|
|
205
287
|
topic: queryText("Topic, keyword, niche, or audience to research on YouTube."),
|
|
@@ -211,7 +293,10 @@ var aivYoutubeTrends = createTool({
|
|
|
211
293
|
var channelEnum = z.enum(["ai_search", "seo_keywords", "google_trends", "reddit", "youtube"]);
|
|
212
294
|
var aivFindContentOpportunities = createTool({
|
|
213
295
|
id: "cite42_find_content_opportunities",
|
|
214
|
-
|
|
296
|
+
mcp: readOnly("Find content opportunities"),
|
|
297
|
+
description: workflowDescription(
|
|
298
|
+
"Run the content-opportunities workflow: fans out across AI search, SEO keywords, trends, Reddit, YouTube, and citations and returns the aggregated data in one response."
|
|
299
|
+
),
|
|
215
300
|
inputSchema: z.object({
|
|
216
301
|
topic: queryText("Market, niche, product category, or audience to research."),
|
|
217
302
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
@@ -224,7 +309,10 @@ var aivFindContentOpportunities = createTool({
|
|
|
224
309
|
});
|
|
225
310
|
var aivAnalyzeTopicDemand = createTool({
|
|
226
311
|
id: "cite42_analyze_topic_demand",
|
|
227
|
-
|
|
312
|
+
mcp: readOnly("Analyze topic demand"),
|
|
313
|
+
description: workflowDescription(
|
|
314
|
+
"Run the topic-demand workflow: SEO keyword data, Google Trends, AI answers, and audience conversations, aggregated in one response."
|
|
315
|
+
),
|
|
228
316
|
inputSchema: z.object({
|
|
229
317
|
topic: queryText("Topic, keyword, category, or audience demand to analyze."),
|
|
230
318
|
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
@@ -234,7 +322,10 @@ var aivAnalyzeTopicDemand = createTool({
|
|
|
234
322
|
});
|
|
235
323
|
var aivMapAiPrompts = createTool({
|
|
236
324
|
id: "cite42_map_ai_prompts",
|
|
237
|
-
|
|
325
|
+
mcp: readOnly("Map buyer prompts for AI search"),
|
|
326
|
+
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."
|
|
328
|
+
),
|
|
238
329
|
inputSchema: z.object({
|
|
239
330
|
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
240
331
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
@@ -247,7 +338,10 @@ var aivMapAiPrompts = createTool({
|
|
|
247
338
|
});
|
|
248
339
|
var aivAnalyzeCompetitorContent = createTool({
|
|
249
340
|
id: "cite42_analyze_competitor_content",
|
|
250
|
-
|
|
341
|
+
mcp: readOnly("Analyze competitor content"),
|
|
342
|
+
description: workflowDescription(
|
|
343
|
+
"Run the competitor-content workflow: AI rankings, head-to-head comparison, citations, keyword demand, and topic coverage, aggregated in one response."
|
|
344
|
+
),
|
|
251
345
|
inputSchema: z.object({
|
|
252
346
|
competitors: competitorList.describe("Competitor brands, domains, or channels to analyze."),
|
|
253
347
|
topic: z.string().trim().min(1).max(QUERY_MAX_CHARS).optional().describe("Optional topic/category to constrain the analysis."),
|
|
@@ -258,7 +352,10 @@ var aivAnalyzeCompetitorContent = createTool({
|
|
|
258
352
|
});
|
|
259
353
|
var aivFindAiCitationGaps = createTool({
|
|
260
354
|
id: "cite42_find_ai_citation_gaps",
|
|
261
|
-
|
|
355
|
+
mcp: readOnly("Find AI citation gaps"),
|
|
356
|
+
description: workflowDescription(
|
|
357
|
+
"Run the AI-citation-gaps workflow: find queries where AI answers cite others but not your brand, domain, or URL, with the supporting data."
|
|
358
|
+
),
|
|
262
359
|
inputSchema: z.object({
|
|
263
360
|
query: queryText("AI-search query to inspect for citation gaps."),
|
|
264
361
|
brand: shortText("Brand or domain that should earn citations."),
|
|
@@ -270,7 +367,10 @@ var aivFindAiCitationGaps = createTool({
|
|
|
270
367
|
});
|
|
271
368
|
var aivGenerateContentBrief = createTool({
|
|
272
369
|
id: "cite42_generate_content_brief",
|
|
273
|
-
|
|
370
|
+
mcp: readOnly("Generate content brief data"),
|
|
371
|
+
description: workflowDescription(
|
|
372
|
+
"Run the content-brief workflow: AI answers, citations, SEO keyword demand, trends, Reddit, and YouTube signals aggregated into brief-ready data."
|
|
373
|
+
),
|
|
274
374
|
inputSchema: z.object({
|
|
275
375
|
topic: queryText("Topic, keyword, or content idea to brief."),
|
|
276
376
|
audience: optionalShortText("Optional target audience or ICP."),
|
|
@@ -281,6 +381,37 @@ var aivGenerateContentBrief = createTool({
|
|
|
281
381
|
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
282
382
|
});
|
|
283
383
|
|
|
384
|
+
// src/server.ts
|
|
385
|
+
var CITE42_TOOLS = {
|
|
386
|
+
cite42_credits: aivCredits,
|
|
387
|
+
cite42_pricing: aivPricing,
|
|
388
|
+
cite42_usage: aivUsage,
|
|
389
|
+
cite42_search: aivSearch,
|
|
390
|
+
cite42_rankings: aivRankings,
|
|
391
|
+
cite42_compare: aivCompare,
|
|
392
|
+
cite42_citations: aivCitations,
|
|
393
|
+
cite42_sentiment: aivSentiment,
|
|
394
|
+
cite42_keywords: aivKeywords,
|
|
395
|
+
cite42_trends: aivTrends,
|
|
396
|
+
cite42_reddit_trends: aivRedditTrends,
|
|
397
|
+
cite42_youtube_trends: aivYoutubeTrends,
|
|
398
|
+
cite42_find_content_opportunities: aivFindContentOpportunities,
|
|
399
|
+
cite42_analyze_topic_demand: aivAnalyzeTopicDemand,
|
|
400
|
+
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
401
|
+
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
402
|
+
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
403
|
+
cite42_generate_content_brief: aivGenerateContentBrief
|
|
404
|
+
};
|
|
405
|
+
function buildCite42Server(version) {
|
|
406
|
+
return new MCPServer({
|
|
407
|
+
id: "cite42",
|
|
408
|
+
name: "Cite42",
|
|
409
|
+
version,
|
|
410
|
+
description: "AI market data API: AI search, citations, SEO keywords, trends, and workflows.",
|
|
411
|
+
tools: CITE42_TOOLS
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
284
415
|
// src/stdio.ts
|
|
285
416
|
if (!resolveApiKey()) {
|
|
286
417
|
console.error(
|
|
@@ -288,30 +419,7 @@ if (!resolveApiKey()) {
|
|
|
288
419
|
);
|
|
289
420
|
process.exit(1);
|
|
290
421
|
}
|
|
291
|
-
|
|
292
|
-
id: "cite42",
|
|
293
|
-
name: "Cite42",
|
|
294
|
-
version: "0.1.15",
|
|
295
|
-
description: "AI market data API: AI search, citations, SEO keywords, trends, and workflows.",
|
|
296
|
-
tools: {
|
|
297
|
-
cite42_search: aivSearch,
|
|
298
|
-
cite42_rankings: aivRankings,
|
|
299
|
-
cite42_compare: aivCompare,
|
|
300
|
-
cite42_citations: aivCitations,
|
|
301
|
-
cite42_sentiment: aivSentiment,
|
|
302
|
-
cite42_keywords: aivKeywords,
|
|
303
|
-
cite42_trends: aivTrends,
|
|
304
|
-
cite42_reddit_trends: aivRedditTrends,
|
|
305
|
-
cite42_youtube_trends: aivYoutubeTrends,
|
|
306
|
-
cite42_find_content_opportunities: aivFindContentOpportunities,
|
|
307
|
-
cite42_analyze_topic_demand: aivAnalyzeTopicDemand,
|
|
308
|
-
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
309
|
-
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
310
|
-
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
311
|
-
cite42_generate_content_brief: aivGenerateContentBrief
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
server.startStdio().catch((error) => {
|
|
422
|
+
buildCite42Server("0.1.16").startStdio().catch((error) => {
|
|
315
423
|
console.error("[@cite42/mcp] Fatal error:", error);
|
|
316
424
|
process.exit(1);
|
|
317
425
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cite42/mcp",
|
|
3
3
|
"mcpName": "dev.cite42/mcp",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.16",
|
|
5
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.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsup && chmod +x dist/stdio.mjs",
|
|
18
18
|
"dev": "tsup --watch",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
19
21
|
"prepublishOnly": "npm run build"
|
|
20
22
|
},
|
|
21
23
|
"dependencies": {
|
|
@@ -24,9 +26,11 @@
|
|
|
24
26
|
"zod": "^4.4.3"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
27
30
|
"@types/node": "^20.19.27",
|
|
28
31
|
"tsup": "^8.0.0",
|
|
29
|
-
"typescript": "^5.9.3"
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"vitest": "^4.1.10"
|
|
30
34
|
},
|
|
31
35
|
"publishConfig": {
|
|
32
36
|
"access": "public"
|