@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 CHANGED
@@ -1,36 +1,31 @@
1
1
  # @cite42/mcp
2
2
 
3
- MCP (Model Context Protocol) server for the [Cite42](https://cite42.dev) AI search data API,
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
- ## Install
5
+ Use Cite42 from your coding agent to:
10
6
 
11
- You don't need to install — `npx` will fetch and run it on demand.
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
- ## Configure Claude Desktop
12
+ Requires a Cite42 API key from [cite42.dev/app/keys](https://cite42.dev/app/keys).
14
13
 
15
- Open your Claude Desktop config:
14
+ ## Claude Code
16
15
 
17
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
18
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
16
+ ```bash
17
+ claude mcp add cite42 -e CITE42_API_KEY=cite42_live_your_key_here -- npx -y @cite42/mcp
18
+ ```
19
19
 
20
- Add the Cite42 MCP server (pass your key as an arg — one flat line):
20
+ ## Codex CLI
21
21
 
22
- ```json
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
- Prefer an environment variable instead? This form works too:
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": { "CITE42_API_KEY": "cite42_live_your_key_here" }
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
- ### `cite42_search`
55
- Run a search query against the AI models and get each model's answer plus the sources it cited.
56
-
57
- ### `cite42_citations`
58
- Look up which sources the AI models cite for a query — useful for tracking which sites get referenced in AI answers. Optionally check whether a specific URL is cited.
59
-
60
- ### `cite42_rankings`
61
- Check how a list of competitor domains/brands rank in AI-generated answers for a query (mention rate, average position, per-model breakdown).
62
-
63
- ### `cite42_compare`
64
- Compare one brand head-to-head against a competitor set, with a co-mention graph of which brands the models name together.
65
-
66
- ### `cite42_sentiment`
67
- Score how each model talks about a brand: a positive/neutral/negative split with the phrases driving the tone, plus an aggregate.
68
-
69
- ### `cite42_keywords`
70
- Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) related keyword ideas.
71
-
72
- ### `cite42_trends`
73
- Google Trends interest-over-time, related/rising queries, and a rising/falling/stable label for a term.
74
-
75
- ## Configuration
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: "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.",
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: import_zod.z.string().min(1).describe("The query to run against the AI models."),
59
- models: import_zod.z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all (chatgpt, claude, perplexity, gemini).")
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: "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.",
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: import_zod.z.string().min(1).describe("The query to look up cited sources for."),
68
- url: import_zod.z.string().optional().describe("Optional URL to check whether it was cited."),
69
- models: import_zod.z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "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.",
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: import_zod.z.string().min(1).describe("The query to measure brand/competitor visibility on."),
78
- competitors: import_zod.z.array(import_zod.z.string().min(1)).nonempty().describe("Brand or domain names to track within the AI-generated answers."),
79
- models: import_zod.z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "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.",
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: import_zod.z.string().min(1).describe("The query to compare the brand and competitors on."),
88
- brand: import_zod.z.string().min(1).describe("Your brand, ranked alongside the competitors."),
89
- competitors: import_zod.z.array(import_zod.z.string().min(1)).nonempty().max(10).describe("Competitor brand/domain names (up to 10) to rank against your brand."),
90
- models: import_zod.z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "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.",
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: import_zod.z.string().min(1).describe("The query whose answers are scored for brand sentiment."),
99
- brand: import_zod.z.string().min(1).describe("The brand to score sentiment for in each model answer."),
100
- models: import_zod.z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) related keyword ideas. A flat-priced data tool, not an AI-model query.",
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: import_zod.z.string().min(1).describe("Seed keyword to fetch search volume, CPC, and competition for."),
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 their volumes.")
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: "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.",
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: import_zod.z.string().min(1).describe("Search term to pull Google Trends interest-over-time for."),
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 search data API: query ChatGPT, Claude, Perplexity, and Gemini.",
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: "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.",
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: z.string().min(1).describe("The query to run against the AI models."),
58
- models: z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all (chatgpt, claude, perplexity, gemini).")
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: "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.",
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: z.string().min(1).describe("The query to look up cited sources for."),
67
- url: z.string().optional().describe("Optional URL to check whether it was cited."),
68
- models: z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "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.",
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: z.string().min(1).describe("The query to measure brand/competitor visibility on."),
77
- competitors: z.array(z.string().min(1)).nonempty().describe("Brand or domain names to track within the AI-generated answers."),
78
- models: z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "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.",
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: z.string().min(1).describe("The query to compare the brand and competitors on."),
87
- brand: z.string().min(1).describe("Your brand, ranked alongside the competitors."),
88
- competitors: z.array(z.string().min(1)).nonempty().max(10).describe("Competitor brand/domain names (up to 10) to rank against your brand."),
89
- models: z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "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.",
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: z.string().min(1).describe("The query whose answers are scored for brand sentiment."),
98
- brand: z.string().min(1).describe("The brand to score sentiment for in each model answer."),
99
- models: z.array(modelEnum).nonempty().optional().describe("Models to query. Defaults to all.")
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: "Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) related keyword ideas. A flat-priced data tool, not an AI-model query.",
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: z.string().min(1).describe("Seed keyword to fetch search volume, CPC, and competition for."),
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 their volumes.")
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: "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.",
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: z.string().min(1).describe("Search term to pull Google Trends interest-over-time for."),
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 search data API: query ChatGPT, Claude, Perplexity, and Gemini.",
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.2",
4
- "description": "MCP server for the Cite42 AI search data API. Use Claude Desktop and other LLM clients to query AI search engines.",
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", "ai", "search", "llm", "claude"],
32
+ "keywords": ["mcp", "model-context-protocol", "cite42", "ai-search", "seo", "keywords", "trends", "claude"],
33
33
  "engines": {
34
34
  "node": ">=18"
35
35
  }