@cite42/mcp 0.1.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 ADDED
@@ -0,0 +1,70 @@
1
+ # @cite42/mcp
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.
8
+
9
+ ## Install
10
+
11
+ You don't need to install — `npx` will fetch and run it on demand.
12
+
13
+ ## Configure Claude Desktop
14
+
15
+ Open your Claude Desktop config:
16
+
17
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
18
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
19
+
20
+ Add the Cite42 MCP server:
21
+
22
+ ```json
23
+ {
24
+ "mcpServers": {
25
+ "cite42": {
26
+ "command": "npx",
27
+ "args": ["-y", "@cite42/mcp"],
28
+ "env": {
29
+ "CITE42_API_KEY": "cite42_live_your_key_here"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ Get your API key from [cite42.dev/app/keys](https://cite42.dev/app/keys).
37
+
38
+ Restart Claude Desktop. You should see seven new tools available (see below).
39
+
40
+ ## Tools
41
+
42
+ ### `cite42_search`
43
+ Run a search query against the AI models and get each model's answer plus the sources it cited.
44
+
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.
47
+
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).
50
+
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.
53
+
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.
56
+
57
+ ### `cite42_keywords`
58
+ Classic keyword data for a seed term: monthly search volume, CPC, competition, and (optionally) related keyword ideas.
59
+
60
+ ### `cite42_trends`
61
+ Google Trends interest-over-time, related/rising queries, and a rising/falling/stable label for a term.
62
+
63
+ ## Environment variables
64
+
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`.
67
+
68
+ ## Pricing
69
+
70
+ Each tool call consumes credits from your Cite42 balance. Top up at [cite42.dev/app/billing](https://cite42.dev/app/billing).
package/dist/stdio.cjs ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/stdio.ts
5
+ var import_mcp = require("@mastra/mcp");
6
+
7
+ // src/tools.ts
8
+ var import_tools = require("@mastra/core/tools");
9
+ var import_zod = require("zod");
10
+
11
+ // src/client.ts
12
+ var DEFAULT_BASE_URL = "https://api.cite42.dev/v1";
13
+ function getApiKey() {
14
+ const key = process.env.CITE42_API_KEY;
15
+ if (!key) {
16
+ throw new Error(
17
+ "CITE42_API_KEY environment variable is required. Set it in your MCP client config (e.g., Claude Desktop)."
18
+ );
19
+ }
20
+ return key;
21
+ }
22
+ function getBaseUrl() {
23
+ return process.env.CITE42_API_BASE || DEFAULT_BASE_URL;
24
+ }
25
+ async function aivPost(path, body) {
26
+ const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
27
+ const res = await fetch(url, {
28
+ method: "POST",
29
+ headers: {
30
+ "Content-Type": "application/json",
31
+ Authorization: `Bearer ${getApiKey()}`
32
+ },
33
+ body: JSON.stringify(body)
34
+ });
35
+ if (!res.ok) {
36
+ const text = await res.text();
37
+ throw new Error(`Cite42 API ${res.status} ${path}: ${text}`);
38
+ }
39
+ return res.json();
40
+ }
41
+
42
+ // src/tools.ts
43
+ var modelEnum = import_zod.z.enum(["chatgpt", "claude", "perplexity", "gemini", "google_ai_overview"]);
44
+ var aivSearch = (0, import_tools.createTool)({
45
+ 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.",
47
+ 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).")
50
+ }),
51
+ execute: async (input) => aivPost("/search", input)
52
+ });
53
+ var aivCitations = (0, import_tools.createTool)({
54
+ 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.",
56
+ 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.")
60
+ }),
61
+ execute: async (input) => aivPost("/citations", input)
62
+ });
63
+ var aivRankings = (0, import_tools.createTool)({
64
+ 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.",
66
+ 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.")
70
+ }),
71
+ execute: async (input) => aivPost("/rankings", input)
72
+ });
73
+ var aivCompare = (0, import_tools.createTool)({
74
+ 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.",
76
+ 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.")
81
+ }),
82
+ execute: async (input) => aivPost("/compare", input)
83
+ });
84
+ var aivSentiment = (0, import_tools.createTool)({
85
+ 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.",
87
+ 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.")
91
+ }),
92
+ execute: async (input) => aivPost("/sentiment", input)
93
+ });
94
+ var aivKeywords = (0, import_tools.createTool)({
95
+ 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.",
97
+ 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.")
101
+ }),
102
+ execute: async (input) => aivPost("/keywords", input)
103
+ });
104
+ var aivTrends = (0, import_tools.createTool)({
105
+ 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.",
107
+ 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.')
111
+ }),
112
+ execute: async (input) => aivPost("/trends", input)
113
+ });
114
+
115
+ // src/stdio.ts
116
+ var server = new import_mcp.MCPServer({
117
+ id: "cite42",
118
+ name: "Cite42",
119
+ version: "0.1.0",
120
+ description: "AI search data API: query ChatGPT, Claude, Perplexity, and Gemini.",
121
+ tools: {
122
+ cite42_search: aivSearch,
123
+ cite42_rankings: aivRankings,
124
+ cite42_compare: aivCompare,
125
+ cite42_citations: aivCitations,
126
+ cite42_sentiment: aivSentiment,
127
+ cite42_keywords: aivKeywords,
128
+ cite42_trends: aivTrends
129
+ }
130
+ });
131
+ server.startStdio().catch((error) => {
132
+ console.error("[@cite42/mcp] Fatal error:", error);
133
+ process.exit(1);
134
+ });
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/stdio.mjs ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/stdio.ts
4
+ import { MCPServer } from "@mastra/mcp";
5
+
6
+ // src/tools.ts
7
+ import { createTool } from "@mastra/core/tools";
8
+ import { z } from "zod";
9
+
10
+ // src/client.ts
11
+ var DEFAULT_BASE_URL = "https://api.cite42.dev/v1";
12
+ function getApiKey() {
13
+ const key = process.env.CITE42_API_KEY;
14
+ if (!key) {
15
+ throw new Error(
16
+ "CITE42_API_KEY environment variable is required. Set it in your MCP client config (e.g., Claude Desktop)."
17
+ );
18
+ }
19
+ return key;
20
+ }
21
+ function getBaseUrl() {
22
+ return process.env.CITE42_API_BASE || DEFAULT_BASE_URL;
23
+ }
24
+ async function aivPost(path, body) {
25
+ const url = `${getBaseUrl()}${path.startsWith("/") ? path : `/${path}`}`;
26
+ const res = await fetch(url, {
27
+ method: "POST",
28
+ headers: {
29
+ "Content-Type": "application/json",
30
+ Authorization: `Bearer ${getApiKey()}`
31
+ },
32
+ body: JSON.stringify(body)
33
+ });
34
+ if (!res.ok) {
35
+ const text = await res.text();
36
+ throw new Error(`Cite42 API ${res.status} ${path}: ${text}`);
37
+ }
38
+ return res.json();
39
+ }
40
+
41
+ // src/tools.ts
42
+ var modelEnum = z.enum(["chatgpt", "claude", "perplexity", "gemini", "google_ai_overview"]);
43
+ var aivSearch = createTool({
44
+ 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.",
46
+ 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).")
49
+ }),
50
+ execute: async (input) => aivPost("/search", input)
51
+ });
52
+ var aivCitations = createTool({
53
+ 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.",
55
+ 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.")
59
+ }),
60
+ execute: async (input) => aivPost("/citations", input)
61
+ });
62
+ var aivRankings = createTool({
63
+ 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.",
65
+ 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.")
69
+ }),
70
+ execute: async (input) => aivPost("/rankings", input)
71
+ });
72
+ var aivCompare = createTool({
73
+ 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.",
75
+ 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.")
80
+ }),
81
+ execute: async (input) => aivPost("/compare", input)
82
+ });
83
+ var aivSentiment = createTool({
84
+ 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.",
86
+ 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.")
90
+ }),
91
+ execute: async (input) => aivPost("/sentiment", input)
92
+ });
93
+ var aivKeywords = createTool({
94
+ 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.",
96
+ 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.")
100
+ }),
101
+ execute: async (input) => aivPost("/keywords", input)
102
+ });
103
+ var aivTrends = createTool({
104
+ 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.",
106
+ 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.')
110
+ }),
111
+ execute: async (input) => aivPost("/trends", input)
112
+ });
113
+
114
+ // src/stdio.ts
115
+ var server = new MCPServer({
116
+ id: "cite42",
117
+ name: "Cite42",
118
+ version: "0.1.0",
119
+ description: "AI search data API: query ChatGPT, Claude, Perplexity, and Gemini.",
120
+ tools: {
121
+ cite42_search: aivSearch,
122
+ cite42_rankings: aivRankings,
123
+ cite42_compare: aivCompare,
124
+ cite42_citations: aivCitations,
125
+ cite42_sentiment: aivSentiment,
126
+ cite42_keywords: aivKeywords,
127
+ cite42_trends: aivTrends
128
+ }
129
+ });
130
+ server.startStdio().catch((error) => {
131
+ console.error("[@cite42/mcp] Fatal error:", error);
132
+ process.exit(1);
133
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@cite42/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for the Cite42 AI search data API. Use Claude Desktop and other LLM clients to query AI search engines.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": "dist/stdio.mjs",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup && chmod +x dist/stdio.mjs",
14
+ "dev": "tsup --watch",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "dependencies": {
18
+ "@mastra/core": "^1.35.0",
19
+ "@mastra/mcp": "^1.7.0",
20
+ "zod": "^4.4.3"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^20.19.27",
24
+ "tsup": "^8.0.0",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "keywords": ["mcp", "model-context-protocol", "ai", "search", "llm", "claude"],
31
+ "engines": {
32
+ "node": ">=18"
33
+ }
34
+ }