@cite42/mcp 0.8.3 → 0.9.0

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
@@ -55,6 +55,14 @@ claude mcp add --transport http --scope user cite42 https://mcp.cite42.dev && cl
55
55
 
56
56
  Your browser opens to sign in. Then start Claude Code, or restart it if it was already open, and check with `/mcp`.
57
57
 
58
+ Or install Cite42 as a plugin from inside Claude Code:
59
+
60
+ ```
61
+ /plugin install cite42 --marketplace Cite42/mcp
62
+ ```
63
+
64
+ Claude Code versions before 2.1.275 need two steps: `/plugin marketplace add Cite42/mcp`, then `/plugin install cite42@cite42`. Then open `/mcp`, pick the Cite42 server and sign in.
65
+
58
66
  ### Codex CLI
59
67
 
60
68
  ```bash
package/dist/stdio.cjs CHANGED
@@ -217,6 +217,15 @@ function optionalShortText(description) {
217
217
  }
218
218
  var modelsInput = import_zod.z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
219
219
  var fullSweepInput = import_zod.z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
220
+ var AI_MARKETS_ENABLED = true;
221
+ var aiMarketInput = AI_MARKETS_ENABLED ? {
222
+ country: import_zod.z.string().trim().max(8).optional().describe(
223
+ `Optional ISO-2 country to measure from, e.g. "gb". Omit for each surface's default. Gemini has no country setting and ignores it.`
224
+ ),
225
+ language: import_zod.z.string().trim().max(8).optional().describe(
226
+ "Optional ISO-2 language for google_ai_overview and chatgpt_app, which read in it. Needs country; defaults to the country's main language."
227
+ )
228
+ } : {};
220
229
  var competitorList = import_zod.z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
221
230
  async function fetchBalance() {
222
231
  const usage = await aivGet("/usage");
@@ -277,7 +286,8 @@ var aivSearch = (0, import_tools.createTool)({
277
286
  inputSchema: import_zod.z.object({
278
287
  query: queryText("The concise AI-search/market-research query to run against the selected models."),
279
288
  models: modelsInput,
280
- fullSweep: fullSweepInput
289
+ fullSweep: fullSweepInput,
290
+ ...aiMarketInput
281
291
  }),
282
292
  execute: async (input) => aivPost("/search", input)
283
293
  });
@@ -289,7 +299,8 @@ var aivCitations = (0, import_tools.createTool)({
289
299
  query: queryText("The concise query to look up cited sources for."),
290
300
  url: import_zod.z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
291
301
  models: modelsInput,
292
- fullSweep: fullSweepInput
302
+ fullSweep: fullSweepInput,
303
+ ...aiMarketInput
293
304
  }),
294
305
  execute: async (input) => aivPost("/citations", input)
295
306
  });
@@ -301,7 +312,8 @@ var aivRankings = (0, import_tools.createTool)({
301
312
  query: queryText("The concise query to measure brand/competitor visibility on."),
302
313
  competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
303
314
  models: modelsInput,
304
- fullSweep: fullSweepInput
315
+ fullSweep: fullSweepInput,
316
+ ...aiMarketInput
305
317
  }),
306
318
  execute: async (input) => aivPost("/rankings", input)
307
319
  });
@@ -314,7 +326,8 @@ var aivCompare = (0, import_tools.createTool)({
314
326
  brand: shortText("Your brand, ranked alongside the competitors."),
315
327
  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."),
316
328
  models: modelsInput,
317
- fullSweep: fullSweepInput
329
+ fullSweep: fullSweepInput,
330
+ ...aiMarketInput
318
331
  }),
319
332
  execute: async (input) => aivPost("/compare", input)
320
333
  });
@@ -326,7 +339,8 @@ var aivSentiment = (0, import_tools.createTool)({
326
339
  query: queryText("The concise query whose answers are scored for brand sentiment."),
327
340
  brand: shortText("The brand to score sentiment for in each model answer."),
328
341
  models: modelsInput,
329
- fullSweep: fullSweepInput
342
+ fullSweep: fullSweepInput,
343
+ ...aiMarketInput
330
344
  }),
331
345
  execute: async (input) => aivPost("/sentiment", input)
332
346
  });
@@ -363,6 +377,24 @@ var aivKeywordsBatch = (0, import_tools.createTool)({
363
377
  }),
364
378
  execute: async (input) => aivPost("/keywords/batch", input)
365
379
  });
380
+ var aivKeywordRankings = (0, import_tools.createTool)({
381
+ id: "cite42_keyword_rankings",
382
+ mcp: readOnly("Get Google keyword rankings"),
383
+ description: `Where a site ranks in Google organic results (top 100) for one to five keywords in one country, desktop or mobile: position, ranking URL, competitor positions, the top 10 and whether the AI Overview cites the site. Billed per keyword checked; a keyword that fails is free. Each check is one live sample. ${PAID_TOOL_BILLING}`,
384
+ inputSchema: import_zod.z.object({
385
+ keywords: import_zod.z.array(import_zod.z.string().trim().min(1).max(200)).min(1).max(5).refine(
386
+ (keywords) => new Set(keywords.map((keyword) => keyword.toLowerCase().replace(/\s+/g, " "))).size === keywords.length,
387
+ "Keywords must be distinct."
388
+ ).describe("One to five distinct search terms, as people type them into Google. Each is billed separately."),
389
+ domain: import_zod.z.string().trim().max(253).optional().describe("Your site, e.g. example.com. Covers www and subdomains. Omit to get only the top results."),
390
+ url: import_zod.z.string().trim().max(500).optional().describe("Optional page that should rank, as a full URL or a path such as /pricing."),
391
+ competitors: import_zod.z.array(import_zod.z.string().trim().min(1).max(253)).max(5).optional().describe("Up to 5 competitor domains to report positions for."),
392
+ country: import_zod.z.string().trim().max(8).optional().describe('ISO-2 country code of the Google market to check. Defaults to "us".'),
393
+ language: import_zod.z.string().trim().max(8).optional().describe("Optional ISO-2 language code. Defaults to the country's main language."),
394
+ device: import_zod.z.enum(["desktop", "mobile"]).optional().describe("Desktop or mobile results. Defaults to desktop.")
395
+ }),
396
+ execute: async (input) => aivPost("/keywords/rankings", input)
397
+ });
366
398
  var aivTrends = (0, import_tools.createTool)({
367
399
  id: "cite42_trends",
368
400
  mcp: readOnly("Get Google Trends data"),
@@ -639,6 +671,7 @@ var CITE42_TOOLS = {
639
671
  cite42_recommendations: aivRecommendations,
640
672
  cite42_keywords: aivKeywords,
641
673
  cite42_keywords_batch: aivKeywordsBatch,
674
+ cite42_keyword_rankings: aivKeywordRankings,
642
675
  cite42_trends: aivTrends,
643
676
  cite42_reddit_trends: aivRedditTrends,
644
677
  cite42_youtube_trends: aivYoutubeTrends,
@@ -674,7 +707,7 @@ if (!resolveApiKey()) {
674
707
  );
675
708
  process.exit(1);
676
709
  }
677
- buildCite42Server("0.8.3").startStdio().catch((error) => {
710
+ buildCite42Server("0.9.0").startStdio().catch((error) => {
678
711
  console.error("[@cite42/mcp] Fatal error:", error);
679
712
  process.exit(1);
680
713
  });
package/dist/stdio.mjs CHANGED
@@ -216,6 +216,15 @@ function optionalShortText(description) {
216
216
  }
217
217
  var modelsInput = z.array(modelEnum).nonempty().optional().describe(MODEL_SELECTION_DESCRIPTION);
218
218
  var fullSweepInput = z.boolean().optional().describe(FULL_SWEEP_DESCRIPTION);
219
+ var AI_MARKETS_ENABLED = true;
220
+ var aiMarketInput = AI_MARKETS_ENABLED ? {
221
+ country: z.string().trim().max(8).optional().describe(
222
+ `Optional ISO-2 country to measure from, e.g. "gb". Omit for each surface's default. Gemini has no country setting and ignores it.`
223
+ ),
224
+ language: z.string().trim().max(8).optional().describe(
225
+ "Optional ISO-2 language for google_ai_overview and chatgpt_app, which read in it. Needs country; defaults to the country's main language."
226
+ )
227
+ } : {};
219
228
  var competitorList = z.array(shortText("Competitor brand/domain name.")).nonempty().max(20);
220
229
  async function fetchBalance() {
221
230
  const usage = await aivGet("/usage");
@@ -276,7 +285,8 @@ var aivSearch = createTool({
276
285
  inputSchema: z.object({
277
286
  query: queryText("The concise AI-search/market-research query to run against the selected models."),
278
287
  models: modelsInput,
279
- fullSweep: fullSweepInput
288
+ fullSweep: fullSweepInput,
289
+ ...aiMarketInput
280
290
  }),
281
291
  execute: async (input) => aivPost("/search", input)
282
292
  });
@@ -288,7 +298,8 @@ var aivCitations = createTool({
288
298
  query: queryText("The concise query to look up cited sources for."),
289
299
  url: z.string().trim().max(500).optional().describe("Optional URL to check whether it was cited."),
290
300
  models: modelsInput,
291
- fullSweep: fullSweepInput
301
+ fullSweep: fullSweepInput,
302
+ ...aiMarketInput
292
303
  }),
293
304
  execute: async (input) => aivPost("/citations", input)
294
305
  });
@@ -300,7 +311,8 @@ var aivRankings = createTool({
300
311
  query: queryText("The concise query to measure brand/competitor visibility on."),
301
312
  competitors: competitorList.describe("Brand or domain names to track within the AI-generated answers."),
302
313
  models: modelsInput,
303
- fullSweep: fullSweepInput
314
+ fullSweep: fullSweepInput,
315
+ ...aiMarketInput
304
316
  }),
305
317
  execute: async (input) => aivPost("/rankings", input)
306
318
  });
@@ -313,7 +325,8 @@ var aivCompare = createTool({
313
325
  brand: shortText("Your brand, ranked alongside the competitors."),
314
326
  competitors: z.array(shortText("Competitor brand/domain name.")).nonempty().max(10).describe("Competitor brand/domain names (up to 10) to rank against your brand."),
315
327
  models: modelsInput,
316
- fullSweep: fullSweepInput
328
+ fullSweep: fullSweepInput,
329
+ ...aiMarketInput
317
330
  }),
318
331
  execute: async (input) => aivPost("/compare", input)
319
332
  });
@@ -325,7 +338,8 @@ var aivSentiment = createTool({
325
338
  query: queryText("The concise query whose answers are scored for brand sentiment."),
326
339
  brand: shortText("The brand to score sentiment for in each model answer."),
327
340
  models: modelsInput,
328
- fullSweep: fullSweepInput
341
+ fullSweep: fullSweepInput,
342
+ ...aiMarketInput
329
343
  }),
330
344
  execute: async (input) => aivPost("/sentiment", input)
331
345
  });
@@ -362,6 +376,24 @@ var aivKeywordsBatch = createTool({
362
376
  }),
363
377
  execute: async (input) => aivPost("/keywords/batch", input)
364
378
  });
379
+ var aivKeywordRankings = createTool({
380
+ id: "cite42_keyword_rankings",
381
+ mcp: readOnly("Get Google keyword rankings"),
382
+ description: `Where a site ranks in Google organic results (top 100) for one to five keywords in one country, desktop or mobile: position, ranking URL, competitor positions, the top 10 and whether the AI Overview cites the site. Billed per keyword checked; a keyword that fails is free. Each check is one live sample. ${PAID_TOOL_BILLING}`,
383
+ inputSchema: z.object({
384
+ keywords: z.array(z.string().trim().min(1).max(200)).min(1).max(5).refine(
385
+ (keywords) => new Set(keywords.map((keyword) => keyword.toLowerCase().replace(/\s+/g, " "))).size === keywords.length,
386
+ "Keywords must be distinct."
387
+ ).describe("One to five distinct search terms, as people type them into Google. Each is billed separately."),
388
+ domain: z.string().trim().max(253).optional().describe("Your site, e.g. example.com. Covers www and subdomains. Omit to get only the top results."),
389
+ url: z.string().trim().max(500).optional().describe("Optional page that should rank, as a full URL or a path such as /pricing."),
390
+ competitors: z.array(z.string().trim().min(1).max(253)).max(5).optional().describe("Up to 5 competitor domains to report positions for."),
391
+ country: z.string().trim().max(8).optional().describe('ISO-2 country code of the Google market to check. Defaults to "us".'),
392
+ language: z.string().trim().max(8).optional().describe("Optional ISO-2 language code. Defaults to the country's main language."),
393
+ device: z.enum(["desktop", "mobile"]).optional().describe("Desktop or mobile results. Defaults to desktop.")
394
+ }),
395
+ execute: async (input) => aivPost("/keywords/rankings", input)
396
+ });
365
397
  var aivTrends = createTool({
366
398
  id: "cite42_trends",
367
399
  mcp: readOnly("Get Google Trends data"),
@@ -638,6 +670,7 @@ var CITE42_TOOLS = {
638
670
  cite42_recommendations: aivRecommendations,
639
671
  cite42_keywords: aivKeywords,
640
672
  cite42_keywords_batch: aivKeywordsBatch,
673
+ cite42_keyword_rankings: aivKeywordRankings,
641
674
  cite42_trends: aivTrends,
642
675
  cite42_reddit_trends: aivRedditTrends,
643
676
  cite42_youtube_trends: aivYoutubeTrends,
@@ -673,7 +706,7 @@ if (!resolveApiKey()) {
673
706
  );
674
707
  process.exit(1);
675
708
  }
676
- buildCite42Server("0.8.3").startStdio().catch((error) => {
709
+ buildCite42Server("0.9.0").startStdio().catch((error) => {
677
710
  console.error("[@cite42/mcp] Fatal error:", error);
678
711
  process.exit(1);
679
712
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cite42/mcp",
3
3
  "mcpName": "dev.cite42/mcp",
4
- "version": "0.8.3",
4
+ "version": "0.9.0",
5
5
  "description": "Cite42 MCP server: AI visibility and SEO data for AI agents. See how the AI models behind ChatGPT, Claude, Perplexity and Gemini, plus Google AI Overviews, mention your brand and which sources they cite, with SEO keyword data, Google, Reddit and YouTube trends, and weekly or monthly trackers that watch a list of prompts over time. Works with Claude Code, Claude Desktop, Codex CLI, Cursor, and any MCP client.",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "@mastra/core": "^1.68.0",
28
28
  "@mastra/mcp": "^1.18.0",
29
- "zod": "^4.4.3"
29
+ "zod": "^4.6.7"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -86,6 +86,7 @@
86
86
  "@modelcontextprotocol/client": "2.3.1",
87
87
  "@modelcontextprotocol/server": "2.3.1",
88
88
  "@modelcontextprotocol/server-legacy": "2.3.1",
89
- "@modelcontextprotocol/node": "2.1.1"
89
+ "@modelcontextprotocol/node": "2.1.1",
90
+ "body-parser@^2.0.0": "^2.4.0"
90
91
  }
91
92
  }