@cite42/mcp 0.1.2 → 0.1.4
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 +42 -62
- package/dist/stdio.cjs +148 -30
- package/dist/stdio.mjs +148 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
# @cite42/mcp
|
|
2
2
|
|
|
3
|
-
MCP
|
|
4
|
-
built on [Mastra](https://mastra.ai). Lets Claude Desktop, Cursor, and other MCP clients
|
|
5
|
-
query competitor rankings, citations, and search results across ChatGPT, Claude, Perplexity,
|
|
6
|
-
Gemini, and Google AI Overviews. Tools forward to the hosted Cite42 REST API using your API
|
|
7
|
-
key, so billing and metering stay server-side.
|
|
3
|
+
Stdio MCP server for [Cite42](https://cite42.dev).
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+
Use Cite42 from your coding agent to:
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
- check AI search answers across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overview
|
|
8
|
+
- find citations, rankings, brand comparisons, and sentiment
|
|
9
|
+
- get SEO keyword data, Google Trends, Reddit trends, and YouTube trends
|
|
10
|
+
- run Cite42 workflows for content, demand, prompts, competitors, and citation gaps
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
Requires a Cite42 API key from [cite42.dev/app/keys](https://cite42.dev/app/keys).
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
## Claude Code
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
-
|
|
16
|
+
```bash
|
|
17
|
+
claude mcp add cite42 -e CITE42_API_KEY=cite42_live_your_key_here -- npx -y @cite42/mcp
|
|
18
|
+
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
## Codex CLI
|
|
21
21
|
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
"mcpServers": {
|
|
25
|
-
"cite42": {
|
|
26
|
-
"command": "npx",
|
|
27
|
-
"args": ["-y", "@cite42/mcp", "--api-key=cite42_live_your_key_here"]
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
22
|
+
```bash
|
|
23
|
+
codex mcp add cite42 --env CITE42_API_KEY=cite42_live_your_key_here -- npx -y @cite42/mcp
|
|
31
24
|
```
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
## Cursor
|
|
27
|
+
|
|
28
|
+
Add this to your MCP config:
|
|
34
29
|
|
|
35
30
|
```json
|
|
36
31
|
{
|
|
@@ -38,50 +33,35 @@ Prefer an environment variable instead? This form works too:
|
|
|
38
33
|
"cite42": {
|
|
39
34
|
"command": "npx",
|
|
40
35
|
"args": ["-y", "@cite42/mcp"],
|
|
41
|
-
"env": {
|
|
36
|
+
"env": {
|
|
37
|
+
"CITE42_API_KEY": "cite42_live_your_key_here"
|
|
38
|
+
}
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
```
|
|
46
43
|
|
|
47
|
-
The same config works in Cursor, Claude Code, VS Code, Windsurf, and Claude Desktop. Get your API
|
|
48
|
-
key from [cite42.dev/app/keys](https://cite42.dev/app/keys).
|
|
49
|
-
|
|
50
|
-
Restart Claude Desktop. You should see seven new tools available (see below).
|
|
51
|
-
|
|
52
44
|
## Tools
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
The key (and optional base URL) can be passed as a CLI flag or an environment variable. A flag
|
|
78
|
-
wins when both are set.
|
|
79
|
-
|
|
80
|
-
| Setting | Flag | Env var | Default |
|
|
81
|
-
| --- | --- | --- | --- |
|
|
82
|
-
| API key (required) | `--api-key=cite42_live_...` | `CITE42_API_KEY` | — |
|
|
83
|
-
| API base URL | `--api-base=...` | `CITE42_API_BASE` | `https://api.cite42.dev/v1` |
|
|
84
|
-
|
|
85
|
-
## Pricing
|
|
86
|
-
|
|
87
|
-
Each tool call consumes credits from your Cite42 balance. Top up at [cite42.dev/app/billing](https://cite42.dev/app/billing).
|
|
46
|
+
- `cite42_search`
|
|
47
|
+
- `cite42_citations`
|
|
48
|
+
- `cite42_rankings`
|
|
49
|
+
- `cite42_compare`
|
|
50
|
+
- `cite42_sentiment`
|
|
51
|
+
- `cite42_keywords`
|
|
52
|
+
- `cite42_trends`
|
|
53
|
+
- `cite42_reddit_trends`
|
|
54
|
+
- `cite42_youtube_trends`
|
|
55
|
+
- `cite42_find_content_opportunities`
|
|
56
|
+
- `cite42_analyze_topic_demand`
|
|
57
|
+
- `cite42_map_ai_prompts`
|
|
58
|
+
- `cite42_analyze_competitor_content`
|
|
59
|
+
- `cite42_find_ai_citation_gaps`
|
|
60
|
+
- `cite42_generate_content_brief`
|
|
61
|
+
|
|
62
|
+
## Notes
|
|
63
|
+
|
|
64
|
+
- This package uses stdio MCP.
|
|
65
|
+
- Tool calls use your Cite42 credits.
|
|
66
|
+
- No API key means no successful tool calls.
|
|
67
|
+
- Optional base URL: `CITE42_API_BASE` or `--api-base=https://...`
|
package/dist/stdio.cjs
CHANGED
|
@@ -51,83 +51,193 @@ async function aivPost(path, body) {
|
|
|
51
51
|
|
|
52
52
|
// src/tools.ts
|
|
53
53
|
var modelEnum = import_zod.z.enum(["chatgpt", "claude", "perplexity", "gemini", "google_ai_overview"]);
|
|
54
|
+
var QUERY_MAX_CHARS = 2e3;
|
|
55
|
+
var SHORT_TEXT_MAX_CHARS = 200;
|
|
56
|
+
var TIMEFRAME_MAX_CHARS = 80;
|
|
57
|
+
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.';
|
|
58
|
+
var MCP_USAGE_GUIDANCE = "Use Cite42 for AI-search, citation, SEO, keyword, trend, and market-research data. Do not use it as a general coding or writing assistant; code-related research is fine when the goal is to compare what AI models recommend or cite. If a plan needs more than 20 endpoint calls or may cost over $5, estimate the cost and ask the user before continuing.";
|
|
59
|
+
var MODEL_PRICING = "Pricing: search/citations/rankings/compare are model-specific: Google AI Overview $0.01, Gemini $0.02, ChatGPT/Claude $0.04, Perplexity $0.05 per selected model. Sentiment: Google AI Overview $0.03, Gemini $0.04, ChatGPT/Claude $0.06, Perplexity $0.07. Omitting models queries all five AI surfaces. Use the models field when the user names a specific surface or asks to reduce cost.";
|
|
60
|
+
var DATA_PRICING = "Pricing: keywords $0.15/call, trends $0.01/call, Reddit trends $0.05/call, YouTube trends $0.05/call.";
|
|
61
|
+
function queryText(description) {
|
|
62
|
+
return import_zod.z.string().trim().min(1).max(QUERY_MAX_CHARS).describe(description);
|
|
63
|
+
}
|
|
64
|
+
function shortText(description) {
|
|
65
|
+
return import_zod.z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).describe(description);
|
|
66
|
+
}
|
|
67
|
+
function optionalShortText(description) {
|
|
68
|
+
return import_zod.z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).optional().describe(description);
|
|
69
|
+
}
|
|
70
|
+
var modelsInput = import_zod.z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
71
|
+
var competitorList = import_zod.z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
54
72
|
var aivSearch = (0, import_tools.createTool)({
|
|
55
73
|
id: "cite42_search",
|
|
56
|
-
description:
|
|
74
|
+
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}`,
|
|
57
75
|
inputSchema: import_zod.z.object({
|
|
58
|
-
query:
|
|
59
|
-
models:
|
|
76
|
+
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
77
|
+
models: modelsInput
|
|
60
78
|
}),
|
|
61
79
|
execute: async (input) => aivPost("/search", input)
|
|
62
80
|
});
|
|
63
81
|
var aivCitations = (0, import_tools.createTool)({
|
|
64
82
|
id: "cite42_citations",
|
|
65
|
-
description:
|
|
83
|
+
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}`,
|
|
66
84
|
inputSchema: import_zod.z.object({
|
|
67
|
-
query:
|
|
68
|
-
url: import_zod.z.string().optional().describe("Optional URL to check whether it was cited."),
|
|
69
|
-
models:
|
|
85
|
+
query: queryText("The concise query to look up cited sources for."),
|
|
86
|
+
url: import_zod.z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
|
|
87
|
+
models: modelsInput
|
|
70
88
|
}),
|
|
71
89
|
execute: async (input) => aivPost("/citations", input)
|
|
72
90
|
});
|
|
73
91
|
var aivRankings = (0, import_tools.createTool)({
|
|
74
92
|
id: "cite42_rankings",
|
|
75
|
-
description:
|
|
93
|
+
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}`,
|
|
76
94
|
inputSchema: import_zod.z.object({
|
|
77
|
-
query:
|
|
78
|
-
competitors:
|
|
79
|
-
models:
|
|
95
|
+
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
96
|
+
competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
|
|
97
|
+
models: modelsInput
|
|
80
98
|
}),
|
|
81
99
|
execute: async (input) => aivPost("/rankings", input)
|
|
82
100
|
});
|
|
83
101
|
var aivCompare = (0, import_tools.createTool)({
|
|
84
102
|
id: "cite42_compare",
|
|
85
|
-
description:
|
|
103
|
+
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}`,
|
|
86
104
|
inputSchema: import_zod.z.object({
|
|
87
|
-
query:
|
|
88
|
-
brand:
|
|
89
|
-
competitors: import_zod.z.array(
|
|
90
|
-
models:
|
|
105
|
+
query: queryText("The concise query to compare the brand and competitors on."),
|
|
106
|
+
brand: shortText("Your brand, ranked alongside the competitors."),
|
|
107
|
+
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."),
|
|
108
|
+
models: modelsInput
|
|
91
109
|
}),
|
|
92
110
|
execute: async (input) => aivPost("/compare", input)
|
|
93
111
|
});
|
|
94
112
|
var aivSentiment = (0, import_tools.createTool)({
|
|
95
113
|
id: "cite42_sentiment",
|
|
96
|
-
description:
|
|
114
|
+
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}`,
|
|
97
115
|
inputSchema: import_zod.z.object({
|
|
98
|
-
query:
|
|
99
|
-
brand:
|
|
100
|
-
models:
|
|
116
|
+
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
117
|
+
brand: shortText("The brand to score sentiment for in each model answer."),
|
|
118
|
+
models: modelsInput
|
|
101
119
|
}),
|
|
102
120
|
execute: async (input) => aivPost("/sentiment", input)
|
|
103
121
|
});
|
|
104
122
|
var aivKeywords = (0, import_tools.createTool)({
|
|
105
123
|
id: "cite42_keywords",
|
|
106
|
-
description:
|
|
124
|
+
description: `Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) LLM-curated related keyword ideas with volume, CPC, competition, and competition level from provider candidates. A flat-priced data tool, not a multi-model AI-search query. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
107
125
|
inputSchema: import_zod.z.object({
|
|
108
|
-
seed:
|
|
109
|
-
country: import_zod.z.string().optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
110
|
-
ideas: import_zod.z.boolean().optional().describe("When true, also return related keyword ideas with
|
|
126
|
+
seed: queryText("Seed keyword or concise topic to fetch search volume, CPC, and competition for."),
|
|
127
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
128
|
+
ideas: import_zod.z.boolean().optional().describe("When true, also return LLM-curated related keyword ideas with volume, CPC, competition, and competition level.")
|
|
111
129
|
}),
|
|
112
130
|
execute: async (input) => aivPost("/keywords", input)
|
|
113
131
|
});
|
|
114
132
|
var aivTrends = (0, import_tools.createTool)({
|
|
115
133
|
id: "cite42_trends",
|
|
116
|
-
description:
|
|
134
|
+
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}`,
|
|
117
135
|
inputSchema: import_zod.z.object({
|
|
118
|
-
term:
|
|
119
|
-
timeframe: import_zod.z.string().optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
|
|
120
|
-
geo: import_zod.z.string().optional().describe('Optional location name (e.g. "United States"). Worldwide when omitted.')
|
|
136
|
+
term: queryText("Search term or concise topic to pull Google Trends interest-over-time for."),
|
|
137
|
+
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".'),
|
|
138
|
+
geo: import_zod.z.string().trim().max(SHORT_TEXT_MAX_CHARS).optional().describe('Optional location name (e.g. "United States"). Worldwide when omitted.')
|
|
121
139
|
}),
|
|
122
140
|
execute: async (input) => aivPost("/trends", input)
|
|
123
141
|
});
|
|
142
|
+
var aivRedditTrends = (0, import_tools.createTool)({
|
|
143
|
+
id: "cite42_reddit_trends",
|
|
144
|
+
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}`,
|
|
145
|
+
inputSchema: import_zod.z.object({
|
|
146
|
+
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
147
|
+
subreddits: import_zod.z.array(shortText("Subreddit name without r/.")).max(20).optional().describe("Optional subreddit names, without r/."),
|
|
148
|
+
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Conversation window, e.g. "past_7_days", "past_30_days", "past_90_days".')
|
|
149
|
+
}),
|
|
150
|
+
execute: async (input) => aivPost("/reddit/trends", input)
|
|
151
|
+
});
|
|
152
|
+
var aivYoutubeTrends = (0, import_tools.createTool)({
|
|
153
|
+
id: "cite42_youtube_trends",
|
|
154
|
+
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}`,
|
|
155
|
+
inputSchema: import_zod.z.object({
|
|
156
|
+
topic: queryText("Topic, keyword, niche, or audience to research on YouTube."),
|
|
157
|
+
region: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country/region code. Defaults to "US".'),
|
|
158
|
+
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Video-market window, e.g. "past_7_days", "past_30_days", "past_90_days".')
|
|
159
|
+
}),
|
|
160
|
+
execute: async (input) => aivPost("/youtube/trends", input)
|
|
161
|
+
});
|
|
162
|
+
var channelEnum = import_zod.z.enum(["ai_search", "seo_keywords", "google_trends", "reddit", "youtube"]);
|
|
163
|
+
var aivFindContentOpportunities = (0, import_tools.createTool)({
|
|
164
|
+
id: "cite42_find_content_opportunities",
|
|
165
|
+
description: `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. ${MCP_USAGE_GUIDANCE}`,
|
|
166
|
+
inputSchema: import_zod.z.object({
|
|
167
|
+
topic: queryText("Market, niche, product category, or audience to research."),
|
|
168
|
+
audience: optionalShortText("Optional target audience or ICP."),
|
|
169
|
+
brand: optionalShortText("Optional brand/domain to account for."),
|
|
170
|
+
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
171
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
172
|
+
channels: import_zod.z.array(channelEnum).optional().describe("Optional data sources to include.")
|
|
173
|
+
}),
|
|
174
|
+
execute: async (input) => aivPost("/workflows/content-opportunities", input)
|
|
175
|
+
});
|
|
176
|
+
var aivAnalyzeTopicDemand = (0, import_tools.createTool)({
|
|
177
|
+
id: "cite42_analyze_topic_demand",
|
|
178
|
+
description: `Run the topic-demand workflow: SEO keyword data, Google Trends, AI answers, and audience conversations, aggregated in one response. ${MCP_USAGE_GUIDANCE}`,
|
|
179
|
+
inputSchema: import_zod.z.object({
|
|
180
|
+
topic: queryText("Topic, keyword, category, or audience demand to analyze."),
|
|
181
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
182
|
+
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
183
|
+
}),
|
|
184
|
+
execute: async (input) => aivPost("/workflows/topic-demand", input)
|
|
185
|
+
});
|
|
186
|
+
var aivMapAiPrompts = (0, import_tools.createTool)({
|
|
187
|
+
id: "cite42_map_ai_prompts",
|
|
188
|
+
description: `Run the AI-prompt-map workflow: discover and cluster buyer prompts worth tracking from AI search, SEO demand, trends, citations, Reddit, and YouTube data. ${MCP_USAGE_GUIDANCE}`,
|
|
189
|
+
inputSchema: import_zod.z.object({
|
|
190
|
+
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
191
|
+
audience: optionalShortText("Optional target audience or ICP."),
|
|
192
|
+
brand: optionalShortText("Optional brand/domain to include in prompt tracking recommendations."),
|
|
193
|
+
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
194
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
195
|
+
timeframe: import_zod.z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
196
|
+
}),
|
|
197
|
+
execute: async (input) => aivPost("/workflows/ai-prompt-map", input)
|
|
198
|
+
});
|
|
199
|
+
var aivAnalyzeCompetitorContent = (0, import_tools.createTool)({
|
|
200
|
+
id: "cite42_analyze_competitor_content",
|
|
201
|
+
description: `Run the competitor-content workflow: AI rankings, head-to-head comparison, citations, keyword demand, and topic coverage, aggregated in one response. ${MCP_USAGE_GUIDANCE}`,
|
|
202
|
+
inputSchema: import_zod.z.object({
|
|
203
|
+
competitors: competitorList.describe("Competitor brands, domains, or channels to analyze."),
|
|
204
|
+
topic: import_zod.z.string().trim().min(1).max(QUERY_MAX_CHARS).optional().describe("Optional topic/category to constrain the analysis."),
|
|
205
|
+
brand: optionalShortText("Optional primary brand/domain to compare against."),
|
|
206
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
207
|
+
}),
|
|
208
|
+
execute: async (input) => aivPost("/workflows/competitor-content", input)
|
|
209
|
+
});
|
|
210
|
+
var aivFindAiCitationGaps = (0, import_tools.createTool)({
|
|
211
|
+
id: "cite42_find_ai_citation_gaps",
|
|
212
|
+
description: `Run the AI-citation-gaps workflow: find queries where AI answers cite others but not your brand, domain, or URL, with the supporting data. ${MCP_USAGE_GUIDANCE}`,
|
|
213
|
+
inputSchema: import_zod.z.object({
|
|
214
|
+
query: queryText("AI-search query to inspect for citation gaps."),
|
|
215
|
+
brand: shortText("Brand or domain that should earn citations."),
|
|
216
|
+
url: import_zod.z.string().trim().max(500).optional().describe("Optional exact URL to check for citations."),
|
|
217
|
+
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
218
|
+
models: modelsInput
|
|
219
|
+
}),
|
|
220
|
+
execute: async (input) => aivPost("/workflows/ai-citation-gaps", input)
|
|
221
|
+
});
|
|
222
|
+
var aivGenerateContentBrief = (0, import_tools.createTool)({
|
|
223
|
+
id: "cite42_generate_content_brief",
|
|
224
|
+
description: `Run the content-brief workflow: AI answers, citations, SEO keyword demand, trends, Reddit, and YouTube signals aggregated into brief-ready data. ${MCP_USAGE_GUIDANCE}`,
|
|
225
|
+
inputSchema: import_zod.z.object({
|
|
226
|
+
topic: queryText("Topic, keyword, or content idea to brief."),
|
|
227
|
+
audience: optionalShortText("Optional target audience or ICP."),
|
|
228
|
+
brand: optionalShortText("Optional brand/domain the brief is for."),
|
|
229
|
+
goal: optionalShortText("Optional content goal, e.g. rank, earn citations, convert."),
|
|
230
|
+
country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
231
|
+
}),
|
|
232
|
+
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
233
|
+
});
|
|
124
234
|
|
|
125
235
|
// src/stdio.ts
|
|
126
236
|
var server = new import_mcp.MCPServer({
|
|
127
237
|
id: "cite42",
|
|
128
238
|
name: "Cite42",
|
|
129
239
|
version: "0.1.0",
|
|
130
|
-
description: "AI
|
|
240
|
+
description: "AI market data API: AI search, citations, SEO keywords, trends, and workflows.",
|
|
131
241
|
tools: {
|
|
132
242
|
cite42_search: aivSearch,
|
|
133
243
|
cite42_rankings: aivRankings,
|
|
@@ -135,7 +245,15 @@ var server = new import_mcp.MCPServer({
|
|
|
135
245
|
cite42_citations: aivCitations,
|
|
136
246
|
cite42_sentiment: aivSentiment,
|
|
137
247
|
cite42_keywords: aivKeywords,
|
|
138
|
-
cite42_trends: aivTrends
|
|
248
|
+
cite42_trends: aivTrends,
|
|
249
|
+
cite42_reddit_trends: aivRedditTrends,
|
|
250
|
+
cite42_youtube_trends: aivYoutubeTrends,
|
|
251
|
+
cite42_find_content_opportunities: aivFindContentOpportunities,
|
|
252
|
+
cite42_analyze_topic_demand: aivAnalyzeTopicDemand,
|
|
253
|
+
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
254
|
+
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
255
|
+
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
256
|
+
cite42_generate_content_brief: aivGenerateContentBrief
|
|
139
257
|
}
|
|
140
258
|
});
|
|
141
259
|
server.startStdio().catch((error) => {
|
package/dist/stdio.mjs
CHANGED
|
@@ -50,83 +50,193 @@ async function aivPost(path, body) {
|
|
|
50
50
|
|
|
51
51
|
// src/tools.ts
|
|
52
52
|
var modelEnum = z.enum(["chatgpt", "claude", "perplexity", "gemini", "google_ai_overview"]);
|
|
53
|
+
var QUERY_MAX_CHARS = 2e3;
|
|
54
|
+
var SHORT_TEXT_MAX_CHARS = 200;
|
|
55
|
+
var TIMEFRAME_MAX_CHARS = 80;
|
|
56
|
+
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.';
|
|
57
|
+
var MCP_USAGE_GUIDANCE = "Use Cite42 for AI-search, citation, SEO, keyword, trend, and market-research data. Do not use it as a general coding or writing assistant; code-related research is fine when the goal is to compare what AI models recommend or cite. If a plan needs more than 20 endpoint calls or may cost over $5, estimate the cost and ask the user before continuing.";
|
|
58
|
+
var MODEL_PRICING = "Pricing: search/citations/rankings/compare are model-specific: Google AI Overview $0.01, Gemini $0.02, ChatGPT/Claude $0.04, Perplexity $0.05 per selected model. Sentiment: Google AI Overview $0.03, Gemini $0.04, ChatGPT/Claude $0.06, Perplexity $0.07. Omitting models queries all five AI surfaces. Use the models field when the user names a specific surface or asks to reduce cost.";
|
|
59
|
+
var DATA_PRICING = "Pricing: keywords $0.15/call, trends $0.01/call, Reddit trends $0.05/call, YouTube trends $0.05/call.";
|
|
60
|
+
function queryText(description) {
|
|
61
|
+
return z.string().trim().min(1).max(QUERY_MAX_CHARS).describe(description);
|
|
62
|
+
}
|
|
63
|
+
function shortText(description) {
|
|
64
|
+
return z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).describe(description);
|
|
65
|
+
}
|
|
66
|
+
function optionalShortText(description) {
|
|
67
|
+
return z.string().trim().min(1).max(SHORT_TEXT_MAX_CHARS).optional().describe(description);
|
|
68
|
+
}
|
|
69
|
+
var modelsInput = z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
|
|
70
|
+
var competitorList = z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
|
|
53
71
|
var aivSearch = createTool({
|
|
54
72
|
id: "cite42_search",
|
|
55
|
-
description:
|
|
73
|
+
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}`,
|
|
56
74
|
inputSchema: z.object({
|
|
57
|
-
query:
|
|
58
|
-
models:
|
|
75
|
+
query: queryText("The concise AI-search/market-research query to run against the selected models."),
|
|
76
|
+
models: modelsInput
|
|
59
77
|
}),
|
|
60
78
|
execute: async (input) => aivPost("/search", input)
|
|
61
79
|
});
|
|
62
80
|
var aivCitations = createTool({
|
|
63
81
|
id: "cite42_citations",
|
|
64
|
-
description:
|
|
82
|
+
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}`,
|
|
65
83
|
inputSchema: z.object({
|
|
66
|
-
query:
|
|
67
|
-
url: z.string().optional().describe("Optional URL to check whether it was cited."),
|
|
68
|
-
models:
|
|
84
|
+
query: queryText("The concise query to look up cited sources for."),
|
|
85
|
+
url: z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
|
|
86
|
+
models: modelsInput
|
|
69
87
|
}),
|
|
70
88
|
execute: async (input) => aivPost("/citations", input)
|
|
71
89
|
});
|
|
72
90
|
var aivRankings = createTool({
|
|
73
91
|
id: "cite42_rankings",
|
|
74
|
-
description:
|
|
92
|
+
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}`,
|
|
75
93
|
inputSchema: z.object({
|
|
76
|
-
query:
|
|
77
|
-
competitors:
|
|
78
|
-
models:
|
|
94
|
+
query: queryText("The concise query to measure brand/competitor visibility on."),
|
|
95
|
+
competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
|
|
96
|
+
models: modelsInput
|
|
79
97
|
}),
|
|
80
98
|
execute: async (input) => aivPost("/rankings", input)
|
|
81
99
|
});
|
|
82
100
|
var aivCompare = createTool({
|
|
83
101
|
id: "cite42_compare",
|
|
84
|
-
description:
|
|
102
|
+
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}`,
|
|
85
103
|
inputSchema: z.object({
|
|
86
|
-
query:
|
|
87
|
-
brand:
|
|
88
|
-
competitors: z.array(
|
|
89
|
-
models:
|
|
104
|
+
query: queryText("The concise query to compare the brand and competitors on."),
|
|
105
|
+
brand: shortText("Your brand, ranked alongside the competitors."),
|
|
106
|
+
competitors: z.array(shortText("Competitor brand/domain name.")).nonempty().max(10).describe("Competitor brand/domain names (up to 10) to rank against your brand."),
|
|
107
|
+
models: modelsInput
|
|
90
108
|
}),
|
|
91
109
|
execute: async (input) => aivPost("/compare", input)
|
|
92
110
|
});
|
|
93
111
|
var aivSentiment = createTool({
|
|
94
112
|
id: "cite42_sentiment",
|
|
95
|
-
description:
|
|
113
|
+
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}`,
|
|
96
114
|
inputSchema: z.object({
|
|
97
|
-
query:
|
|
98
|
-
brand:
|
|
99
|
-
models:
|
|
115
|
+
query: queryText("The concise query whose answers are scored for brand sentiment."),
|
|
116
|
+
brand: shortText("The brand to score sentiment for in each model answer."),
|
|
117
|
+
models: modelsInput
|
|
100
118
|
}),
|
|
101
119
|
execute: async (input) => aivPost("/sentiment", input)
|
|
102
120
|
});
|
|
103
121
|
var aivKeywords = createTool({
|
|
104
122
|
id: "cite42_keywords",
|
|
105
|
-
description:
|
|
123
|
+
description: `Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) LLM-curated related keyword ideas with volume, CPC, competition, and competition level from provider candidates. A flat-priced data tool, not a multi-model AI-search query. ${DATA_PRICING} ${MCP_USAGE_GUIDANCE}`,
|
|
106
124
|
inputSchema: z.object({
|
|
107
|
-
seed:
|
|
108
|
-
country: z.string().optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
109
|
-
ideas: z.boolean().optional().describe("When true, also return related keyword ideas with
|
|
125
|
+
seed: queryText("Seed keyword or concise topic to fetch search volume, CPC, and competition for."),
|
|
126
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
|
|
127
|
+
ideas: z.boolean().optional().describe("When true, also return LLM-curated related keyword ideas with volume, CPC, competition, and competition level.")
|
|
110
128
|
}),
|
|
111
129
|
execute: async (input) => aivPost("/keywords", input)
|
|
112
130
|
});
|
|
113
131
|
var aivTrends = createTool({
|
|
114
132
|
id: "cite42_trends",
|
|
115
|
-
description:
|
|
133
|
+
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}`,
|
|
116
134
|
inputSchema: z.object({
|
|
117
|
-
term:
|
|
118
|
-
timeframe: z.string().optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
|
|
119
|
-
geo: z.string().optional().describe('Optional location name (e.g. "United States"). Worldwide when omitted.')
|
|
135
|
+
term: queryText("Search term or concise topic to pull Google Trends interest-over-time for."),
|
|
136
|
+
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
|
|
137
|
+
geo: z.string().trim().max(SHORT_TEXT_MAX_CHARS).optional().describe('Optional location name (e.g. "United States"). Worldwide when omitted.')
|
|
120
138
|
}),
|
|
121
139
|
execute: async (input) => aivPost("/trends", input)
|
|
122
140
|
});
|
|
141
|
+
var aivRedditTrends = createTool({
|
|
142
|
+
id: "cite42_reddit_trends",
|
|
143
|
+
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}`,
|
|
144
|
+
inputSchema: z.object({
|
|
145
|
+
topic: queryText("Topic, category, product, or audience to research on Reddit."),
|
|
146
|
+
subreddits: z.array(shortText("Subreddit name without r/.")).max(20).optional().describe("Optional subreddit names, without r/."),
|
|
147
|
+
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Conversation window, e.g. "past_7_days", "past_30_days", "past_90_days".')
|
|
148
|
+
}),
|
|
149
|
+
execute: async (input) => aivPost("/reddit/trends", input)
|
|
150
|
+
});
|
|
151
|
+
var aivYoutubeTrends = createTool({
|
|
152
|
+
id: "cite42_youtube_trends",
|
|
153
|
+
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}`,
|
|
154
|
+
inputSchema: z.object({
|
|
155
|
+
topic: queryText("Topic, keyword, niche, or audience to research on YouTube."),
|
|
156
|
+
region: z.string().trim().max(8).optional().describe('ISO-2 country/region code. Defaults to "US".'),
|
|
157
|
+
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Video-market window, e.g. "past_7_days", "past_30_days", "past_90_days".')
|
|
158
|
+
}),
|
|
159
|
+
execute: async (input) => aivPost("/youtube/trends", input)
|
|
160
|
+
});
|
|
161
|
+
var channelEnum = z.enum(["ai_search", "seo_keywords", "google_trends", "reddit", "youtube"]);
|
|
162
|
+
var aivFindContentOpportunities = createTool({
|
|
163
|
+
id: "cite42_find_content_opportunities",
|
|
164
|
+
description: `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. ${MCP_USAGE_GUIDANCE}`,
|
|
165
|
+
inputSchema: z.object({
|
|
166
|
+
topic: queryText("Market, niche, product category, or audience to research."),
|
|
167
|
+
audience: optionalShortText("Optional target audience or ICP."),
|
|
168
|
+
brand: optionalShortText("Optional brand/domain to account for."),
|
|
169
|
+
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
170
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
171
|
+
channels: z.array(channelEnum).optional().describe("Optional data sources to include.")
|
|
172
|
+
}),
|
|
173
|
+
execute: async (input) => aivPost("/workflows/content-opportunities", input)
|
|
174
|
+
});
|
|
175
|
+
var aivAnalyzeTopicDemand = createTool({
|
|
176
|
+
id: "cite42_analyze_topic_demand",
|
|
177
|
+
description: `Run the topic-demand workflow: SEO keyword data, Google Trends, AI answers, and audience conversations, aggregated in one response. ${MCP_USAGE_GUIDANCE}`,
|
|
178
|
+
inputSchema: z.object({
|
|
179
|
+
topic: queryText("Topic, keyword, category, or audience demand to analyze."),
|
|
180
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
181
|
+
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
182
|
+
}),
|
|
183
|
+
execute: async (input) => aivPost("/workflows/topic-demand", input)
|
|
184
|
+
});
|
|
185
|
+
var aivMapAiPrompts = createTool({
|
|
186
|
+
id: "cite42_map_ai_prompts",
|
|
187
|
+
description: `Run the AI-prompt-map workflow: discover and cluster buyer prompts worth tracking from AI search, SEO demand, trends, citations, Reddit, and YouTube data. ${MCP_USAGE_GUIDANCE}`,
|
|
188
|
+
inputSchema: z.object({
|
|
189
|
+
topic: queryText("Market, product category, buyer problem, or audience to map prompts for."),
|
|
190
|
+
audience: optionalShortText("Optional target audience or ICP."),
|
|
191
|
+
brand: optionalShortText("Optional brand/domain to include in prompt tracking recommendations."),
|
|
192
|
+
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
193
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".'),
|
|
194
|
+
timeframe: z.string().trim().max(TIMEFRAME_MAX_CHARS).optional().describe('Trend window. Defaults to "past_12_months".')
|
|
195
|
+
}),
|
|
196
|
+
execute: async (input) => aivPost("/workflows/ai-prompt-map", input)
|
|
197
|
+
});
|
|
198
|
+
var aivAnalyzeCompetitorContent = createTool({
|
|
199
|
+
id: "cite42_analyze_competitor_content",
|
|
200
|
+
description: `Run the competitor-content workflow: AI rankings, head-to-head comparison, citations, keyword demand, and topic coverage, aggregated in one response. ${MCP_USAGE_GUIDANCE}`,
|
|
201
|
+
inputSchema: z.object({
|
|
202
|
+
competitors: competitorList.describe("Competitor brands, domains, or channels to analyze."),
|
|
203
|
+
topic: z.string().trim().min(1).max(QUERY_MAX_CHARS).optional().describe("Optional topic/category to constrain the analysis."),
|
|
204
|
+
brand: optionalShortText("Optional primary brand/domain to compare against."),
|
|
205
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
206
|
+
}),
|
|
207
|
+
execute: async (input) => aivPost("/workflows/competitor-content", input)
|
|
208
|
+
});
|
|
209
|
+
var aivFindAiCitationGaps = createTool({
|
|
210
|
+
id: "cite42_find_ai_citation_gaps",
|
|
211
|
+
description: `Run the AI-citation-gaps workflow: find queries where AI answers cite others but not your brand, domain, or URL, with the supporting data. ${MCP_USAGE_GUIDANCE}`,
|
|
212
|
+
inputSchema: z.object({
|
|
213
|
+
query: queryText("AI-search query to inspect for citation gaps."),
|
|
214
|
+
brand: shortText("Brand or domain that should earn citations."),
|
|
215
|
+
url: z.string().trim().max(500).optional().describe("Optional exact URL to check for citations."),
|
|
216
|
+
competitors: competitorList.optional().describe("Competitor brands/domains."),
|
|
217
|
+
models: modelsInput
|
|
218
|
+
}),
|
|
219
|
+
execute: async (input) => aivPost("/workflows/ai-citation-gaps", input)
|
|
220
|
+
});
|
|
221
|
+
var aivGenerateContentBrief = createTool({
|
|
222
|
+
id: "cite42_generate_content_brief",
|
|
223
|
+
description: `Run the content-brief workflow: AI answers, citations, SEO keyword demand, trends, Reddit, and YouTube signals aggregated into brief-ready data. ${MCP_USAGE_GUIDANCE}`,
|
|
224
|
+
inputSchema: z.object({
|
|
225
|
+
topic: queryText("Topic, keyword, or content idea to brief."),
|
|
226
|
+
audience: optionalShortText("Optional target audience or ICP."),
|
|
227
|
+
brand: optionalShortText("Optional brand/domain the brief is for."),
|
|
228
|
+
goal: optionalShortText("Optional content goal, e.g. rank, earn citations, convert."),
|
|
229
|
+
country: z.string().trim().max(8).optional().describe('ISO-2 country code. Defaults to "us".')
|
|
230
|
+
}),
|
|
231
|
+
execute: async (input) => aivPost("/workflows/content-brief", input)
|
|
232
|
+
});
|
|
123
233
|
|
|
124
234
|
// src/stdio.ts
|
|
125
235
|
var server = new MCPServer({
|
|
126
236
|
id: "cite42",
|
|
127
237
|
name: "Cite42",
|
|
128
238
|
version: "0.1.0",
|
|
129
|
-
description: "AI
|
|
239
|
+
description: "AI market data API: AI search, citations, SEO keywords, trends, and workflows.",
|
|
130
240
|
tools: {
|
|
131
241
|
cite42_search: aivSearch,
|
|
132
242
|
cite42_rankings: aivRankings,
|
|
@@ -134,7 +244,15 @@ var server = new MCPServer({
|
|
|
134
244
|
cite42_citations: aivCitations,
|
|
135
245
|
cite42_sentiment: aivSentiment,
|
|
136
246
|
cite42_keywords: aivKeywords,
|
|
137
|
-
cite42_trends: aivTrends
|
|
247
|
+
cite42_trends: aivTrends,
|
|
248
|
+
cite42_reddit_trends: aivRedditTrends,
|
|
249
|
+
cite42_youtube_trends: aivYoutubeTrends,
|
|
250
|
+
cite42_find_content_opportunities: aivFindContentOpportunities,
|
|
251
|
+
cite42_analyze_topic_demand: aivAnalyzeTopicDemand,
|
|
252
|
+
cite42_map_ai_prompts: aivMapAiPrompts,
|
|
253
|
+
cite42_analyze_competitor_content: aivAnalyzeCompetitorContent,
|
|
254
|
+
cite42_find_ai_citation_gaps: aivFindAiCitationGaps,
|
|
255
|
+
cite42_generate_content_brief: aivGenerateContentBrief
|
|
138
256
|
}
|
|
139
257
|
});
|
|
140
258
|
server.startStdio().catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cite42/mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "MCP server for
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Stdio MCP server for Cite42 AI search, citations, SEO keywords, trends, and workflows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
|
-
"keywords": ["mcp", "model-context-protocol", "
|
|
32
|
+
"keywords": ["mcp", "model-context-protocol", "cite42", "ai-search", "seo", "keywords", "trends", "claude"],
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=18"
|
|
35
35
|
}
|