@cite42/mcp 0.1.1 → 0.1.3

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,70 +1,156 @@
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
+ MCP (Model Context Protocol) server for the [Cite42](https://cite42.dev) API, built on
4
+ [Mastra](https://mastra.ai).
5
+
6
+ Cite42 lets Claude Desktop, Cursor, Claude Code, VS Code, Windsurf, and other MCP clients query
7
+ AI-search visibility, citations, SEO keyword data, Google Trends, Reddit/YouTube signals, and
8
+ creator/agency workflow contracts.
9
+
10
+ This package is a thin MCP client. It forwards tool calls to the hosted Cite42 REST API, so billing,
11
+ metering, provider keys, caching, and rate limits stay server-side.
12
+
13
+ ## Requirements
14
+
15
+ - Node.js 18 or newer
16
+ - A Cite42 API key
17
+ - Prepaid Cite42 credits
18
+
19
+ Get an API key from [cite42.dev/app/keys](https://cite42.dev/app/keys). Top up credits at
20
+ [cite42.dev/app/billing](https://cite42.dev/app/billing).
21
+
22
+ Without an API key, the MCP server starts but tool calls cannot reach the Cite42 API.
8
23
 
9
24
  ## Install
10
25
 
11
- You don't need to install — `npx` will fetch and run it on demand.
26
+ You do not need to install this package globally. Use `npx`; your MCP client will fetch and run the
27
+ latest published version.
12
28
 
13
29
  ## Configure Claude Desktop
14
30
 
15
31
  Open your Claude Desktop config:
16
32
 
17
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
18
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
33
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
34
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
19
35
 
20
36
  Add the Cite42 MCP server:
21
37
 
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "cite42": {
42
+ "command": "npx",
43
+ "args": ["-y", "@cite42/mcp", "--api-key=cite42_live_your_key_here"]
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ You can also pass the key as an environment variable:
50
+
22
51
  ```json
23
52
  {
24
53
  "mcpServers": {
25
54
  "cite42": {
26
55
  "command": "npx",
27
56
  "args": ["-y", "@cite42/mcp"],
28
- "env": {
29
- "CITE42_API_KEY": "cite42_live_your_key_here"
30
- }
57
+ "env": { "CITE42_API_KEY": "cite42_live_your_key_here" }
31
58
  }
32
59
  }
33
60
  }
34
61
  ```
35
62
 
36
- Get your API key from [cite42.dev/app/keys](https://cite42.dev/app/keys).
63
+ Restart your MCP client after editing the config.
64
+
65
+ ## Configuration
37
66
 
38
- Restart Claude Desktop. You should see seven new tools available (see below).
67
+ | Setting | Flag | Env var | Default |
68
+ | --- | --- | --- | --- |
69
+ | API key | `--api-key=cite42_live_...` | `CITE42_API_KEY` | Required |
70
+ | API base URL | `--api-base=...` | `CITE42_API_BASE` | `https://api.cite42.dev/v1` |
71
+
72
+ Flags win over environment variables when both are set.
39
73
 
40
74
  ## Tools
41
75
 
42
- ### `cite42_search`
43
- Run a search query against the AI models and get each model's answer plus the sources it cited.
76
+ | Tool | What it does |
77
+ | --- | --- |
78
+ | `cite42_search` | Runs a query across ChatGPT, Claude, Perplexity, Gemini, and Google AI Overview, returning answers and cited sources. |
79
+ | `cite42_citations` | Aggregates cited URLs for a query and can check whether a specific URL is cited. |
80
+ | `cite42_rankings` | Measures competitor/brand mention rate, average position, and per-model ranking breakdown. |
81
+ | `cite42_compare` | Compares one brand against competitors, including co-mention data. |
82
+ | `cite42_sentiment` | Scores positive/neutral/negative brand sentiment with supporting phrases. |
83
+ | `cite42_keywords` | Returns search volume, CPC, competition, competition level, and optional curated keyword ideas. |
84
+ | `cite42_trends` | Returns Google Trends interest, related/rising queries, and trend direction. |
85
+ | `cite42_reddit_trends` | Returns Reddit audience questions, pain points, threads, phrases, mentioned products, and URLs. |
86
+ | `cite42_youtube_trends` | Returns YouTube rising videos, creator angles, title patterns, channels, and opportunities. |
87
+ | `cite42_find_content_opportunities` | Combines AI search, SEO keywords, trends, Reddit, YouTube, and citations. |
88
+ | `cite42_analyze_topic_demand` | Combines SEO demand, trends, AI answers, and audience conversations for a topic. |
89
+ | `cite42_map_ai_prompts` | Discovers buyer prompts worth tracking from AI, SEO, trend, Reddit, and YouTube data. |
90
+ | `cite42_analyze_competitor_content` | Combines AI rankings, comparisons, citations, keyword demand, and topic coverage. |
91
+ | `cite42_find_ai_citation_gaps` | Finds AI queries where competitors are cited but your brand/domain/URL is not. |
92
+ | `cite42_generate_content_brief` | Builds brief-ready data from AI answers, citations, SEO demand, trends, Reddit, and YouTube. |
93
+
94
+ ## Model Selection
95
+
96
+ The AI-search tools accept an optional `models` array:
44
97
 
45
- ### `cite42_citations`
46
- 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.
98
+ ```json
99
+ {
100
+ "query": "best customer support software for B2B SaaS",
101
+ "models": ["perplexity", "gemini"]
102
+ }
103
+ ```
47
104
 
48
- ### `cite42_rankings`
49
- Check how a list of competitor domains/brands rank in AI-generated answers for a query (mention rate, average position, per-model breakdown).
105
+ Omit `models` to query all five surfaces: `chatgpt`, `claude`, `perplexity`, `gemini`, and
106
+ `google_ai_overview`.
50
107
 
51
- ### `cite42_compare`
52
- Compare one brand head-to-head against a competitor set, with a co-mention graph of which brands the models name together.
108
+ ## Keyword Ideas
53
109
 
54
- ### `cite42_sentiment`
55
- Score how each model talks about a brand: a positive/neutral/negative split with the phrases driving the tone, plus an aggregate.
110
+ `cite42_keywords` can return curated keyword ideas when `ideas` is true:
56
111
 
57
- ### `cite42_keywords`
58
- Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) related keyword ideas.
112
+ ```json
113
+ {
114
+ "seed": "customer service software",
115
+ "country": "us",
116
+ "ideas": true
117
+ }
118
+ ```
59
119
 
60
- ### `cite42_trends`
61
- Google Trends interest-over-time, related/rising queries, and a rising/falling/stable label for a term.
120
+ Each idea includes provider-backed metrics:
62
121
 
63
- ## Environment variables
122
+ ```json
123
+ {
124
+ "keyword": "customer support software",
125
+ "volume": 1900,
126
+ "cpc": 116.7,
127
+ "competition": 0.15,
128
+ "competitionLevel": "LOW"
129
+ }
130
+ ```
64
131
 
65
- - `CITE42_API_KEY` (required) — Your Cite42 API key (`cite42_live_...`)
66
- - `CITE42_API_BASE` (optional) — Override the API base URL. Defaults to `https://api.cite42.dev/v1`.
132
+ The curation step selects from provider candidates only; it does not invent new keyword strings.
67
133
 
68
134
  ## Pricing
69
135
 
70
- Each tool call consumes credits from your Cite42 balance. Top up at [cite42.dev/app/billing](https://cite42.dev/app/billing).
136
+ Each successful tool call consumes credits from your Cite42 balance.
137
+
138
+ | Tool group | Price |
139
+ | --- | --- |
140
+ | Search, citations, rankings, compare | Google AI Overview $0.01, Gemini $0.02, ChatGPT/Claude $0.04, Perplexity $0.05 per selected model |
141
+ | Sentiment | Google AI Overview $0.03, Gemini $0.04, ChatGPT/Claude $0.06, Perplexity $0.07 per selected model |
142
+ | Keywords | $0.15 per call |
143
+ | Google Trends | $0.01 per call |
144
+ | Reddit trends | $0.05 per call |
145
+ | YouTube trends | $0.05 per call |
146
+
147
+ Workflow tools are priced from the primitive tools they run. Check the Cite42 dashboard for current
148
+ usage and balance.
149
+
150
+ ## Usage Guidance
151
+
152
+ Use Cite42 for AI-search, citation, SEO, keyword, trend, and market-research data. It is not a
153
+ general coding or writing assistant. Code-related research is fine when the goal is to compare what
154
+ AI models recommend or cite.
155
+
156
+ If a task may require many calls or may cost more than a few dollars, ask the user before continuing.
package/dist/stdio.cjs CHANGED
@@ -10,17 +10,27 @@ var import_zod = require("zod");
10
10
 
11
11
  // src/client.ts
12
12
  var DEFAULT_BASE_URL = "https://api.cite42.dev/v1";
13
+ function readFlag(name) {
14
+ const argv = process.argv.slice(2);
15
+ const prefix = `--${name}=`;
16
+ for (let i = 0; i < argv.length; i++) {
17
+ const arg = argv[i];
18
+ if (arg.startsWith(prefix)) return arg.slice(prefix.length);
19
+ if (arg === `--${name}` && i + 1 < argv.length) return argv[i + 1];
20
+ }
21
+ return void 0;
22
+ }
13
23
  function getApiKey() {
14
- const key = process.env.CITE42_API_KEY;
24
+ const key = readFlag("api-key") ?? process.env.CITE42_API_KEY;
15
25
  if (!key) {
16
26
  throw new Error(
17
- "CITE42_API_KEY environment variable is required. Set it in your MCP client config (e.g., Claude Desktop)."
27
+ "No Cite42 API key found. Pass --api-key=cite42_live_... in the server args, or set the CITE42_API_KEY environment variable in your MCP client config."
18
28
  );
19
29
  }
20
30
  return key;
21
31
  }
22
32
  function getBaseUrl() {
23
- return process.env.CITE42_API_BASE || DEFAULT_BASE_URL;
33
+ return readFlag("api-base") ?? process.env.CITE42_API_BASE ?? DEFAULT_BASE_URL;
24
34
  }
25
35
  async function aivPost(path, body) {
26
36
  const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
@@ -41,83 +51,193 @@ async function aivPost(path, body) {
41
51
 
42
52
  // src/tools.ts
43
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);
44
72
  var aivSearch = (0, import_tools.createTool)({
45
73
  id: "cite42_search",
46
- 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}`,
47
75
  inputSchema: import_zod.z.object({
48
- query: import_zod.z.string().min(1).describe("The query to run against the AI models."),
49
- 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
50
78
  }),
51
79
  execute: async (input) => aivPost("/search", input)
52
80
  });
53
81
  var aivCitations = (0, import_tools.createTool)({
54
82
  id: "cite42_citations",
55
- 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}`,
56
84
  inputSchema: import_zod.z.object({
57
- query: import_zod.z.string().min(1).describe("The query to look up cited sources for."),
58
- url: import_zod.z.string().optional().describe("Optional URL to check whether it was cited."),
59
- 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
60
88
  }),
61
89
  execute: async (input) => aivPost("/citations", input)
62
90
  });
63
91
  var aivRankings = (0, import_tools.createTool)({
64
92
  id: "cite42_rankings",
65
- 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}`,
66
94
  inputSchema: import_zod.z.object({
67
- query: import_zod.z.string().min(1).describe("The query to measure brand/competitor visibility on."),
68
- competitors: import_zod.z.array(import_zod.z.string().min(1)).nonempty().describe("Brand or domain names to track within the AI-generated answers."),
69
- 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
70
98
  }),
71
99
  execute: async (input) => aivPost("/rankings", input)
72
100
  });
73
101
  var aivCompare = (0, import_tools.createTool)({
74
102
  id: "cite42_compare",
75
- 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}`,
76
104
  inputSchema: import_zod.z.object({
77
- query: import_zod.z.string().min(1).describe("The query to compare the brand and competitors on."),
78
- brand: import_zod.z.string().min(1).describe("Your brand, ranked alongside the competitors."),
79
- 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."),
80
- 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
81
109
  }),
82
110
  execute: async (input) => aivPost("/compare", input)
83
111
  });
84
112
  var aivSentiment = (0, import_tools.createTool)({
85
113
  id: "cite42_sentiment",
86
- 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}`,
87
115
  inputSchema: import_zod.z.object({
88
- query: import_zod.z.string().min(1).describe("The query whose answers are scored for brand sentiment."),
89
- brand: import_zod.z.string().min(1).describe("The brand to score sentiment for in each model answer."),
90
- 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
91
119
  }),
92
120
  execute: async (input) => aivPost("/sentiment", input)
93
121
  });
94
122
  var aivKeywords = (0, import_tools.createTool)({
95
123
  id: "cite42_keywords",
96
- 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}`,
97
125
  inputSchema: import_zod.z.object({
98
- seed: import_zod.z.string().min(1).describe("Seed keyword to fetch search volume, CPC, and competition for."),
99
- country: import_zod.z.string().optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
100
- 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.")
101
129
  }),
102
130
  execute: async (input) => aivPost("/keywords", input)
103
131
  });
104
132
  var aivTrends = (0, import_tools.createTool)({
105
133
  id: "cite42_trends",
106
- 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}`,
107
135
  inputSchema: import_zod.z.object({
108
- term: import_zod.z.string().min(1).describe("Search term to pull Google Trends interest-over-time for."),
109
- timeframe: import_zod.z.string().optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
110
- 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.')
111
139
  }),
112
140
  execute: async (input) => aivPost("/trends", input)
113
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
+ });
114
234
 
115
235
  // src/stdio.ts
116
236
  var server = new import_mcp.MCPServer({
117
237
  id: "cite42",
118
238
  name: "Cite42",
119
239
  version: "0.1.0",
120
- 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.",
121
241
  tools: {
122
242
  cite42_search: aivSearch,
123
243
  cite42_rankings: aivRankings,
@@ -125,7 +245,15 @@ var server = new import_mcp.MCPServer({
125
245
  cite42_citations: aivCitations,
126
246
  cite42_sentiment: aivSentiment,
127
247
  cite42_keywords: aivKeywords,
128
- 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
129
257
  }
130
258
  });
131
259
  server.startStdio().catch((error) => {
package/dist/stdio.mjs CHANGED
@@ -9,17 +9,27 @@ import { z } from "zod";
9
9
 
10
10
  // src/client.ts
11
11
  var DEFAULT_BASE_URL = "https://api.cite42.dev/v1";
12
+ function readFlag(name) {
13
+ const argv = process.argv.slice(2);
14
+ const prefix = `--${name}=`;
15
+ for (let i = 0; i < argv.length; i++) {
16
+ const arg = argv[i];
17
+ if (arg.startsWith(prefix)) return arg.slice(prefix.length);
18
+ if (arg === `--${name}` && i + 1 < argv.length) return argv[i + 1];
19
+ }
20
+ return void 0;
21
+ }
12
22
  function getApiKey() {
13
- const key = process.env.CITE42_API_KEY;
23
+ const key = readFlag("api-key") ?? process.env.CITE42_API_KEY;
14
24
  if (!key) {
15
25
  throw new Error(
16
- "CITE42_API_KEY environment variable is required. Set it in your MCP client config (e.g., Claude Desktop)."
26
+ "No Cite42 API key found. Pass --api-key=cite42_live_... in the server args, or set the CITE42_API_KEY environment variable in your MCP client config."
17
27
  );
18
28
  }
19
29
  return key;
20
30
  }
21
31
  function getBaseUrl() {
22
- return process.env.CITE42_API_BASE || DEFAULT_BASE_URL;
32
+ return readFlag("api-base") ?? process.env.CITE42_API_BASE ?? DEFAULT_BASE_URL;
23
33
  }
24
34
  async function aivPost(path, body) {
25
35
  const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
@@ -40,83 +50,193 @@ async function aivPost(path, body) {
40
50
 
41
51
  // src/tools.ts
42
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);
43
71
  var aivSearch = createTool({
44
72
  id: "cite42_search",
45
- 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}`,
46
74
  inputSchema: z.object({
47
- query: z.string().min(1).describe("The query to run against the AI models."),
48
- 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
49
77
  }),
50
78
  execute: async (input) => aivPost("/search", input)
51
79
  });
52
80
  var aivCitations = createTool({
53
81
  id: "cite42_citations",
54
- 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}`,
55
83
  inputSchema: z.object({
56
- query: z.string().min(1).describe("The query to look up cited sources for."),
57
- url: z.string().optional().describe("Optional URL to check whether it was cited."),
58
- 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
59
87
  }),
60
88
  execute: async (input) => aivPost("/citations", input)
61
89
  });
62
90
  var aivRankings = createTool({
63
91
  id: "cite42_rankings",
64
- 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}`,
65
93
  inputSchema: z.object({
66
- query: z.string().min(1).describe("The query to measure brand/competitor visibility on."),
67
- competitors: z.array(z.string().min(1)).nonempty().describe("Brand or domain names to track within the AI-generated answers."),
68
- 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
69
97
  }),
70
98
  execute: async (input) => aivPost("/rankings", input)
71
99
  });
72
100
  var aivCompare = createTool({
73
101
  id: "cite42_compare",
74
- 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}`,
75
103
  inputSchema: z.object({
76
- query: z.string().min(1).describe("The query to compare the brand and competitors on."),
77
- brand: z.string().min(1).describe("Your brand, ranked alongside the competitors."),
78
- competitors: z.array(z.string().min(1)).nonempty().max(10).describe("Competitor brand/domain names (up to 10) to rank against your brand."),
79
- 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
80
108
  }),
81
109
  execute: async (input) => aivPost("/compare", input)
82
110
  });
83
111
  var aivSentiment = createTool({
84
112
  id: "cite42_sentiment",
85
- 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}`,
86
114
  inputSchema: z.object({
87
- query: z.string().min(1).describe("The query whose answers are scored for brand sentiment."),
88
- brand: z.string().min(1).describe("The brand to score sentiment for in each model answer."),
89
- 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
90
118
  }),
91
119
  execute: async (input) => aivPost("/sentiment", input)
92
120
  });
93
121
  var aivKeywords = createTool({
94
122
  id: "cite42_keywords",
95
- 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}`,
96
124
  inputSchema: z.object({
97
- seed: z.string().min(1).describe("Seed keyword to fetch search volume, CPC, and competition for."),
98
- country: z.string().optional().describe('ISO-2 country code for localized metrics. Defaults to "us".'),
99
- 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.")
100
128
  }),
101
129
  execute: async (input) => aivPost("/keywords", input)
102
130
  });
103
131
  var aivTrends = createTool({
104
132
  id: "cite42_trends",
105
- 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}`,
106
134
  inputSchema: z.object({
107
- term: z.string().min(1).describe("Search term to pull Google Trends interest-over-time for."),
108
- timeframe: z.string().optional().describe('Trends window, e.g. "past_12_months", "past_30_days", "past_5_years".'),
109
- 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.')
110
138
  }),
111
139
  execute: async (input) => aivPost("/trends", input)
112
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
+ });
113
233
 
114
234
  // src/stdio.ts
115
235
  var server = new MCPServer({
116
236
  id: "cite42",
117
237
  name: "Cite42",
118
238
  version: "0.1.0",
119
- 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.",
120
240
  tools: {
121
241
  cite42_search: aivSearch,
122
242
  cite42_rankings: aivRankings,
@@ -124,7 +244,15 @@ var server = new MCPServer({
124
244
  cite42_citations: aivCitations,
125
245
  cite42_sentiment: aivSentiment,
126
246
  cite42_keywords: aivKeywords,
127
- 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
128
256
  }
129
257
  });
130
258
  server.startStdio().catch((error) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cite42/mcp",
3
- "version": "0.1.1",
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.3",
4
+ "description": "MCP server for the Cite42 API. Requires a Cite42 API key for 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
  }