@cite42/mcp 0.1.2 → 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,23 +1,39 @@
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
- Add the Cite42 MCP server (pass your key as an arg — one flat line):
36
+ Add the Cite42 MCP server:
21
37
 
22
38
  ```json
23
39
  {
@@ -30,7 +46,7 @@ Add the Cite42 MCP server (pass your key as an arg — one flat line):
30
46
  }
31
47
  ```
32
48
 
33
- Prefer an environment variable instead? This form works too:
49
+ You can also pass the key as an environment variable:
34
50
 
35
51
  ```json
36
52
  {
@@ -44,44 +60,97 @@ Prefer an environment variable instead? This form works too:
44
60
  }
45
61
  ```
46
62
 
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).
63
+ Restart your MCP client after editing the config.
49
64
 
50
- Restart Claude Desktop. You should see seven new tools available (see below).
65
+ ## Configuration
51
66
 
52
- ## Tools
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` |
53
71
 
54
- ### `cite42_search`
55
- Run a search query against the AI models and get each model's answer plus the sources it cited.
72
+ Flags win over environment variables when both are set.
56
73
 
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.
74
+ ## Tools
59
75
 
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).
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:
62
97
 
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.
98
+ ```json
99
+ {
100
+ "query": "best customer support software for B2B SaaS",
101
+ "models": ["perplexity", "gemini"]
102
+ }
103
+ ```
65
104
 
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.
105
+ Omit `models` to query all five surfaces: `chatgpt`, `claude`, `perplexity`, `gemini`, and
106
+ `google_ai_overview`.
68
107
 
69
- ### `cite42_keywords`
70
- Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) related keyword ideas.
108
+ ## Keyword Ideas
71
109
 
72
- ### `cite42_trends`
73
- Google Trends interest-over-time, related/rising queries, and a rising/falling/stable label for a term.
110
+ `cite42_keywords` can return curated keyword ideas when `ideas` is true:
74
111
 
75
- ## Configuration
112
+ ```json
113
+ {
114
+ "seed": "customer service software",
115
+ "country": "us",
116
+ "ideas": true
117
+ }
118
+ ```
76
119
 
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.
120
+ Each idea includes provider-backed metrics:
79
121
 
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` |
122
+ ```json
123
+ {
124
+ "keyword": "customer support software",
125
+ "volume": 1900,
126
+ "cpc": 116.7,
127
+ "competition": 0.15,
128
+ "competitionLevel": "LOW"
129
+ }
130
+ ```
131
+
132
+ The curation step selects from provider candidates only; it does not invent new keyword strings.
84
133
 
85
134
  ## Pricing
86
135
 
87
- 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
@@ -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.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
  }