@geogenio/mcp-server 1.0.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 +116 -0
- package/build/api-client.js +118 -0
- package/build/index.js +47 -0
- package/build/tools.js +402 -0
- package/package.json +26 -0
- package/src/api-client.ts +256 -0
- package/src/index.ts +57 -0
- package/src/tools.ts +520 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# GeoGen MCP Server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for the GeoGen LLM SEO tracking platform. Lets AI assistants query your LLM visibility data, manage entities, and analyze trends via natural language.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Install & Build
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd mcp-server
|
|
11
|
+
npm install
|
|
12
|
+
npm run build
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. Get Your API Key
|
|
16
|
+
|
|
17
|
+
Go to your GeoGen workspace **Settings > API Keys** and create a new key.
|
|
18
|
+
|
|
19
|
+
### 3. Configure Your AI Client
|
|
20
|
+
|
|
21
|
+
#### Claude Desktop
|
|
22
|
+
|
|
23
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"geogen": {
|
|
29
|
+
"command": "node",
|
|
30
|
+
"args": ["/absolute/path/to/mcp-server/build/index.js"],
|
|
31
|
+
"env": {
|
|
32
|
+
"GEOGEN_API_KEY": "your-api-key-here",
|
|
33
|
+
"GEOGEN_BASE_URL": "https://your-app.convex.site"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### Claude Code
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
claude mcp add geogen -- node /absolute/path/to/mcp-server/build/index.js
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then set the environment variables in your shell or `.env` file:
|
|
47
|
+
```bash
|
|
48
|
+
export GEOGEN_API_KEY="your-api-key-here"
|
|
49
|
+
export GEOGEN_BASE_URL="https://your-app.convex.site"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Cursor / Windsurf
|
|
53
|
+
|
|
54
|
+
Add to your MCP settings (see each IDE's documentation for the config location):
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"geogen": {
|
|
59
|
+
"command": "node",
|
|
60
|
+
"args": ["/absolute/path/to/mcp-server/build/index.js"],
|
|
61
|
+
"env": {
|
|
62
|
+
"GEOGEN_API_KEY": "your-api-key-here",
|
|
63
|
+
"GEOGEN_BASE_URL": "https://your-app.convex.site"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Available Tools
|
|
70
|
+
|
|
71
|
+
### Read Operations
|
|
72
|
+
|
|
73
|
+
| Tool | Description |
|
|
74
|
+
|------|-------------|
|
|
75
|
+
| `get_entities` | List all tracked websites/entities |
|
|
76
|
+
| `get_workspace` | Workspace usage, limits, credit balance |
|
|
77
|
+
| `get_workspace_members` | List team members |
|
|
78
|
+
| `get_workspace_tags` | List all tags |
|
|
79
|
+
| `get_models` | Available LLM models |
|
|
80
|
+
| `get_entity_prompts` | Prompts with stats for an entity |
|
|
81
|
+
| `get_responses` | LLM responses with mention status |
|
|
82
|
+
| `get_response_details` | Detailed response data (mentions, citations, fanouts) |
|
|
83
|
+
| `get_citations` | Top cited domains for an entity |
|
|
84
|
+
| `get_citation_details` | Detailed URLs for a cited domain |
|
|
85
|
+
| `get_competitors` | Competitor visibility leaderboard |
|
|
86
|
+
| `get_citations_trend` | Daily citation trend for top cited domains over time |
|
|
87
|
+
| `get_visibility_trend` | Visibility trend over time |
|
|
88
|
+
| `get_sentiment_trend` | Sentiment trend over time |
|
|
89
|
+
| `get_query_fanouts` | Web search queries LLMs perform |
|
|
90
|
+
|
|
91
|
+
### Write Operations
|
|
92
|
+
|
|
93
|
+
| Tool | Description |
|
|
94
|
+
|------|-------------|
|
|
95
|
+
| `create_entity` | Create a new tracked entity (consumes credits) |
|
|
96
|
+
| `add_prompts` | Add single or bulk prompts to an entity |
|
|
97
|
+
| `delete_prompt` | Delete a prompt |
|
|
98
|
+
|
|
99
|
+
## Example Queries
|
|
100
|
+
|
|
101
|
+
Once configured, you can ask your AI assistant things like:
|
|
102
|
+
|
|
103
|
+
- *"List all my tracked entities"*
|
|
104
|
+
- *"What's the visibility trend for entity X over the last 30 days?"*
|
|
105
|
+
- *"Show me the top competitors for my website"*
|
|
106
|
+
- *"Which domains are LLMs citing when they talk about my brand?"*
|
|
107
|
+
- *"What search queries are LLMs running about my entity?"*
|
|
108
|
+
- *"Add a new tracking prompt: 'What is the best project management tool?'"*
|
|
109
|
+
- *"Give me a full SEO report comparing my entity against competitors"*
|
|
110
|
+
|
|
111
|
+
## Environment Variables
|
|
112
|
+
|
|
113
|
+
| Variable | Required | Description |
|
|
114
|
+
|----------|----------|-------------|
|
|
115
|
+
| `GEOGEN_API_KEY` | Yes | Your GeoGen workspace API key |
|
|
116
|
+
| `GEOGEN_BASE_URL` | Yes | Your Convex site URL (e.g. `https://your-app.convex.site`) |
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoGen API Client
|
|
3
|
+
* Wraps all /v1 REST endpoints for use by MCP tool handlers.
|
|
4
|
+
*/
|
|
5
|
+
export class GeoGenApiClient {
|
|
6
|
+
baseUrl;
|
|
7
|
+
apiKey;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
// Strip trailing slash from base URL
|
|
10
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
11
|
+
this.apiKey = config.apiKey;
|
|
12
|
+
}
|
|
13
|
+
async request(path, options) {
|
|
14
|
+
const url = `${this.baseUrl}${path}`;
|
|
15
|
+
const response = await fetch(url, {
|
|
16
|
+
...options,
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
20
|
+
...options?.headers,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const errorMsg = data?.error || data?.message || `HTTP ${response.status}`;
|
|
26
|
+
throw new Error(errorMsg);
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
buildQuery(params) {
|
|
31
|
+
const entries = Object.entries(params).filter((entry) => entry[1] !== undefined && entry[1] !== "");
|
|
32
|
+
if (entries.length === 0)
|
|
33
|
+
return "";
|
|
34
|
+
return "?" + new URLSearchParams(entries).toString();
|
|
35
|
+
}
|
|
36
|
+
// ── Entities ──────────────────────────────────────────────
|
|
37
|
+
async getEntities() {
|
|
38
|
+
return this.request("/v1/entities");
|
|
39
|
+
}
|
|
40
|
+
async createEntity(body) {
|
|
41
|
+
return this.request("/v1/entities", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
body: JSON.stringify(body),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// ── Prompts ───────────────────────────────────────────────
|
|
47
|
+
async getEntityPrompts(params) {
|
|
48
|
+
const query = this.buildQuery(params);
|
|
49
|
+
return this.request(`/v1/entities/prompts${query}`);
|
|
50
|
+
}
|
|
51
|
+
async addPrompts(body) {
|
|
52
|
+
return this.request("/v1/entities/addprompt", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
body: JSON.stringify(body),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async deletePrompt(promptId) {
|
|
58
|
+
const query = this.buildQuery({ promptId });
|
|
59
|
+
return this.request(`/v1/entities/deleteprompt${query}`, {
|
|
60
|
+
method: "DELETE",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// ── Workspace ─────────────────────────────────────────────
|
|
64
|
+
async getWorkspace() {
|
|
65
|
+
return this.request("/v1/workspace");
|
|
66
|
+
}
|
|
67
|
+
async getWorkspaceMembers() {
|
|
68
|
+
return this.request("/v1/workspace/members");
|
|
69
|
+
}
|
|
70
|
+
async getWorkspaceTags() {
|
|
71
|
+
return this.request("/v1/workspace/tags");
|
|
72
|
+
}
|
|
73
|
+
// ── Models ────────────────────────────────────────────────
|
|
74
|
+
async getModels() {
|
|
75
|
+
return this.request("/v1/models");
|
|
76
|
+
}
|
|
77
|
+
// ── Responses ─────────────────────────────────────────────
|
|
78
|
+
async getResponses(params) {
|
|
79
|
+
const query = this.buildQuery(params);
|
|
80
|
+
return this.request(`/v1/responses${query}`);
|
|
81
|
+
}
|
|
82
|
+
async getResponseDetails(params) {
|
|
83
|
+
const query = this.buildQuery(params);
|
|
84
|
+
return this.request(`/v1/responses/details${query}`);
|
|
85
|
+
}
|
|
86
|
+
// ── Citations ─────────────────────────────────────────────
|
|
87
|
+
async getCitations(params) {
|
|
88
|
+
const query = this.buildQuery(params);
|
|
89
|
+
return this.request(`/v1/entities/citations${query}`);
|
|
90
|
+
}
|
|
91
|
+
async getCitationsTrend(params) {
|
|
92
|
+
const query = this.buildQuery(params);
|
|
93
|
+
return this.request(`/v1/trends/citations${query}`);
|
|
94
|
+
}
|
|
95
|
+
async getCitationDetails(params) {
|
|
96
|
+
const query = this.buildQuery(params);
|
|
97
|
+
return this.request(`/v1/entities/citations/details${query}`);
|
|
98
|
+
}
|
|
99
|
+
// ── Competitors ───────────────────────────────────────────
|
|
100
|
+
async getCompetitors(params) {
|
|
101
|
+
const query = this.buildQuery(params);
|
|
102
|
+
return this.request(`/v1/competitors${query}`);
|
|
103
|
+
}
|
|
104
|
+
// ── Trends ────────────────────────────────────────────────
|
|
105
|
+
async getVisibilityTrend(params) {
|
|
106
|
+
const query = this.buildQuery(params);
|
|
107
|
+
return this.request(`/v1/trends/visibility${query}`);
|
|
108
|
+
}
|
|
109
|
+
async getSentimentTrend(params) {
|
|
110
|
+
const query = this.buildQuery(params);
|
|
111
|
+
return this.request(`/v1/trends/sentiment${query}`);
|
|
112
|
+
}
|
|
113
|
+
// ── Query Fanouts ─────────────────────────────────────────
|
|
114
|
+
async getQueryFanouts(params) {
|
|
115
|
+
const query = this.buildQuery(params);
|
|
116
|
+
return this.request(`/v1/query-fanouts${query}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GeoGen MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes the GeoGen LLM SEO tracking API as MCP tools
|
|
6
|
+
* for AI assistants (Claude Desktop, Cursor, Windsurf, Claude Code, etc.)
|
|
7
|
+
*
|
|
8
|
+
* Configuration via environment variables:
|
|
9
|
+
* GEOGEN_API_KEY - Your GeoGen workspace API key (required)
|
|
10
|
+
* GEOGEN_BASE_URL - Your Convex site URL (required)
|
|
11
|
+
*/
|
|
12
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
|
+
import { GeoGenApiClient } from "./api-client.js";
|
|
15
|
+
import { registerTools } from "./tools.js";
|
|
16
|
+
function main() {
|
|
17
|
+
const apiKey = process.env.GEOGEN_API_KEY;
|
|
18
|
+
const baseUrl = process.env.GEOGEN_BASE_URL;
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
console.error("Error: GEOGEN_API_KEY environment variable is required.");
|
|
21
|
+
console.error("Set it to your GeoGen workspace API key.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (!baseUrl) {
|
|
25
|
+
console.error("Error: GEOGEN_BASE_URL environment variable is required.");
|
|
26
|
+
console.error("Set it to your Convex site URL (e.g. https://your-app.convex.site).");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
// Create the API client
|
|
30
|
+
const client = new GeoGenApiClient({ baseUrl, apiKey });
|
|
31
|
+
// Create the MCP server
|
|
32
|
+
const server = new McpServer({
|
|
33
|
+
name: "geogen",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
});
|
|
36
|
+
// Register all tools
|
|
37
|
+
registerTools(server, client);
|
|
38
|
+
// Connect via stdio transport and start
|
|
39
|
+
const transport = new StdioServerTransport();
|
|
40
|
+
server.connect(transport).then(() => {
|
|
41
|
+
console.error("GeoGen MCP Server running on stdio");
|
|
42
|
+
}).catch((error) => {
|
|
43
|
+
console.error("Failed to start GeoGen MCP Server:", error);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
main();
|
package/build/tools.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoGen MCP Tool Definitions
|
|
3
|
+
* Registers all tools on the MCP server, mapping to the /v1 REST API.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
/** Helper: wrap API responses as MCP text content */
|
|
7
|
+
function jsonContent(data) {
|
|
8
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
9
|
+
}
|
|
10
|
+
/** Helper: wrap errors as MCP text content */
|
|
11
|
+
function errorContent(err) {
|
|
12
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
15
|
+
isError: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Reusable schema fragments
|
|
19
|
+
const periodSchema = z
|
|
20
|
+
.enum(["7d", "14d", "30d"])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Time period: 7d, 14d, or 30d (default: 30d)");
|
|
23
|
+
const startDateSchema = z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Start date (ISO format, e.g. 2025-01-01). Use with endDate instead of period.");
|
|
27
|
+
const endDateSchema = z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("End date (ISO format, e.g. 2025-01-31). Use with startDate instead of period.");
|
|
31
|
+
const entityIdSchema = z.string().describe("The entity ID to query");
|
|
32
|
+
const modelsSchema = z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Comma-separated model UUIDs to filter by");
|
|
36
|
+
const tagsSchema = z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Comma-separated tag IDs to filter by");
|
|
40
|
+
const promptIdsSchema = z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Comma-separated prompt IDs to filter by");
|
|
44
|
+
const limitSchema = z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.positive()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Max number of results to return");
|
|
50
|
+
const offsetSchema = z
|
|
51
|
+
.number()
|
|
52
|
+
.int()
|
|
53
|
+
.min(0)
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Number of results to skip (for pagination)");
|
|
56
|
+
export function registerTools(server, client) {
|
|
57
|
+
// ────────────────────────────────────────────────────────────
|
|
58
|
+
// 1. GET ENTITIES
|
|
59
|
+
// ────────────────────────────────────────────────────────────
|
|
60
|
+
server.tool("get_entities", "List all tracked websites/entities in your GeoGen workspace", {}, async () => {
|
|
61
|
+
try {
|
|
62
|
+
return jsonContent(await client.getEntities());
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return errorContent(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// ────────────────────────────────────────────────────────────
|
|
69
|
+
// 2. CREATE ENTITY
|
|
70
|
+
// ────────────────────────────────────────────────────────────
|
|
71
|
+
server.tool("create_entity", "Create a new tracked entity (website/brand) with optional AI-generated prompts. This consumes credits.", {
|
|
72
|
+
name: z.string().describe("Display name for the entity (e.g. 'My Company')"),
|
|
73
|
+
domain: z.string().describe("Domain to track (e.g. 'example.com')"),
|
|
74
|
+
description: z.string().optional().describe("Description of the entity for context"),
|
|
75
|
+
language: z.string().optional().describe("Language code (e.g. 'en', 'fr')"),
|
|
76
|
+
geolocation: z.string().optional().describe("Geolocation code"),
|
|
77
|
+
models: z.array(z.string()).optional().describe("Array of model UUIDs to track against"),
|
|
78
|
+
generatePrompts: z
|
|
79
|
+
.boolean()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Auto-generate tracking prompts with AI (costs credits, default: true)"),
|
|
82
|
+
startTracking: z
|
|
83
|
+
.boolean()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Start tracking immediately (default: true)"),
|
|
86
|
+
}, async (args) => {
|
|
87
|
+
try {
|
|
88
|
+
return jsonContent(await client.createEntity(args));
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return errorContent(err);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
// ────────────────────────────────────────────────────────────
|
|
95
|
+
// 3. GET WORKSPACE
|
|
96
|
+
// ────────────────────────────────────────────────────────────
|
|
97
|
+
server.tool("get_workspace", "Get workspace usage, subscription, limits, and credit balance", {}, async () => {
|
|
98
|
+
try {
|
|
99
|
+
return jsonContent(await client.getWorkspace());
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return errorContent(err);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// ────────────────────────────────────────────────────────────
|
|
106
|
+
// 4. GET WORKSPACE MEMBERS
|
|
107
|
+
// ────────────────────────────────────────────────────────────
|
|
108
|
+
server.tool("get_workspace_members", "List all team members in the workspace", {}, async () => {
|
|
109
|
+
try {
|
|
110
|
+
return jsonContent(await client.getWorkspaceMembers());
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
return errorContent(err);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// ────────────────────────────────────────────────────────────
|
|
117
|
+
// 5. GET WORKSPACE TAGS
|
|
118
|
+
// ────────────────────────────────────────────────────────────
|
|
119
|
+
server.tool("get_workspace_tags", "List all tags in the workspace (used to filter prompts)", {}, async () => {
|
|
120
|
+
try {
|
|
121
|
+
return jsonContent(await client.getWorkspaceTags());
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
return errorContent(err);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// ────────────────────────────────────────────────────────────
|
|
128
|
+
// 6. GET MODELS
|
|
129
|
+
// ────────────────────────────────────────────────────────────
|
|
130
|
+
server.tool("get_models", "List all available LLM models that can be tracked (ChatGPT, Claude, Perplexity, etc.)", {}, async () => {
|
|
131
|
+
try {
|
|
132
|
+
return jsonContent(await client.getModels());
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
return errorContent(err);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// ────────────────────────────────────────────────────────────
|
|
139
|
+
// 7. GET ENTITY PROMPTS
|
|
140
|
+
// ────────────────────────────────────────────────────────────
|
|
141
|
+
server.tool("get_entity_prompts", "Get all tracking prompts for a specific entity, with stats (visibility, mention rate, sentiment)", {
|
|
142
|
+
entityId: entityIdSchema,
|
|
143
|
+
period: periodSchema,
|
|
144
|
+
startDate: startDateSchema,
|
|
145
|
+
endDate: endDateSchema,
|
|
146
|
+
tags: tagsSchema,
|
|
147
|
+
}, async (args) => {
|
|
148
|
+
try {
|
|
149
|
+
return jsonContent(await client.getEntityPrompts(args));
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
return errorContent(err);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// ────────────────────────────────────────────────────────────
|
|
156
|
+
// 8. ADD PROMPTS
|
|
157
|
+
// ────────────────────────────────────────────────────────────
|
|
158
|
+
server.tool("add_prompts", "Add one or more tracking prompts to an entity. Single: provide 'prompt'. Bulk: provide 'prompts' array.", {
|
|
159
|
+
entityId: entityIdSchema,
|
|
160
|
+
prompt: z.string().optional().describe("Single prompt text to add"),
|
|
161
|
+
prompts: z
|
|
162
|
+
.array(z.object({
|
|
163
|
+
prompt: z.string().describe("Prompt text"),
|
|
164
|
+
language: z.string().optional().describe("Language code"),
|
|
165
|
+
geolocation: z.string().optional().describe("Geolocation code"),
|
|
166
|
+
}))
|
|
167
|
+
.optional()
|
|
168
|
+
.describe("Array of prompts for bulk upload"),
|
|
169
|
+
language: z.string().optional().describe("Language for single prompt"),
|
|
170
|
+
geolocation: z.string().optional().describe("Geolocation for single prompt"),
|
|
171
|
+
}, async (args) => {
|
|
172
|
+
try {
|
|
173
|
+
return jsonContent(await client.addPrompts(args));
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
return errorContent(err);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
// ────────────────────────────────────────────────────────────
|
|
180
|
+
// 9. DELETE PROMPT
|
|
181
|
+
// ────────────────────────────────────────────────────────────
|
|
182
|
+
server.tool("delete_prompt", "Delete a tracking prompt by its ID", {
|
|
183
|
+
promptId: z.string().describe("The prompt ID to delete"),
|
|
184
|
+
}, async ({ promptId }) => {
|
|
185
|
+
try {
|
|
186
|
+
return jsonContent(await client.deletePrompt(promptId));
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
return errorContent(err);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
// ────────────────────────────────────────────────────────────
|
|
193
|
+
// 10. GET RESPONSES
|
|
194
|
+
// ────────────────────────────────────────────────────────────
|
|
195
|
+
server.tool("get_responses", "Get LLM responses for an entity with mention status. Filter by period, models, tags, or mention status.", {
|
|
196
|
+
entityId: entityIdSchema,
|
|
197
|
+
period: periodSchema,
|
|
198
|
+
startDate: startDateSchema,
|
|
199
|
+
endDate: endDateSchema,
|
|
200
|
+
models: modelsSchema,
|
|
201
|
+
tags: tagsSchema,
|
|
202
|
+
promptIds: promptIdsSchema,
|
|
203
|
+
mentionStatus: z
|
|
204
|
+
.enum(["all", "mentioned", "not-mentioned"])
|
|
205
|
+
.optional()
|
|
206
|
+
.describe("Filter by mention status"),
|
|
207
|
+
limit: limitSchema,
|
|
208
|
+
offset: offsetSchema,
|
|
209
|
+
}, async (args) => {
|
|
210
|
+
try {
|
|
211
|
+
return jsonContent(await client.getResponses({
|
|
212
|
+
...args,
|
|
213
|
+
limit: args.limit?.toString(),
|
|
214
|
+
offset: args.offset?.toString(),
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
return errorContent(err);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
// ────────────────────────────────────────────────────────────
|
|
222
|
+
// 11. GET RESPONSE DETAILS
|
|
223
|
+
// ────────────────────────────────────────────────────────────
|
|
224
|
+
server.tool("get_response_details", "Get detailed data for a single LLM response, including mentions, citations, and query fanouts", {
|
|
225
|
+
responseId: z.string().describe("The response ID"),
|
|
226
|
+
entityId: entityIdSchema,
|
|
227
|
+
}, async (args) => {
|
|
228
|
+
try {
|
|
229
|
+
return jsonContent(await client.getResponseDetails(args));
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
return errorContent(err);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// ────────────────────────────────────────────────────────────
|
|
236
|
+
// 12. GET CITATIONS
|
|
237
|
+
// ────────────────────────────────────────────────────────────
|
|
238
|
+
server.tool("get_citations", "Get top cited domains for an entity — shows which websites LLMs reference when responding about this entity", {
|
|
239
|
+
entityId: entityIdSchema,
|
|
240
|
+
period: periodSchema,
|
|
241
|
+
startDate: startDateSchema,
|
|
242
|
+
endDate: endDateSchema,
|
|
243
|
+
models: modelsSchema,
|
|
244
|
+
tags: tagsSchema,
|
|
245
|
+
promptIds: promptIdsSchema,
|
|
246
|
+
limit: limitSchema,
|
|
247
|
+
offset: offsetSchema,
|
|
248
|
+
}, async (args) => {
|
|
249
|
+
try {
|
|
250
|
+
return jsonContent(await client.getCitations({
|
|
251
|
+
...args,
|
|
252
|
+
limit: args.limit?.toString(),
|
|
253
|
+
offset: args.offset?.toString(),
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
return errorContent(err);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
// ────────────────────────────────────────────────────────────
|
|
261
|
+
// 13. GET CITATIONS TREND
|
|
262
|
+
// ────────────────────────────────────────────────────────────
|
|
263
|
+
server.tool("get_citations_trend", "Get daily citation trend for top cited domains over time — shows how citation counts change day by day", {
|
|
264
|
+
entityId: entityIdSchema,
|
|
265
|
+
period: periodSchema,
|
|
266
|
+
startDate: startDateSchema,
|
|
267
|
+
endDate: endDateSchema,
|
|
268
|
+
models: modelsSchema,
|
|
269
|
+
tags: tagsSchema,
|
|
270
|
+
promptIds: promptIdsSchema,
|
|
271
|
+
topN: z
|
|
272
|
+
.number()
|
|
273
|
+
.int()
|
|
274
|
+
.positive()
|
|
275
|
+
.optional()
|
|
276
|
+
.describe("Number of top cited domains to include in the trend (default: all)"),
|
|
277
|
+
}, async (args) => {
|
|
278
|
+
try {
|
|
279
|
+
return jsonContent(await client.getCitationsTrend({
|
|
280
|
+
...args,
|
|
281
|
+
topN: args.topN?.toString(),
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
return errorContent(err);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
// ────────────────────────────────────────────────────────────
|
|
289
|
+
// 14. GET CITATION DETAILS
|
|
290
|
+
// ────────────────────────────────────────────────────────────
|
|
291
|
+
server.tool("get_citation_details", "Get detailed URLs for a specific cited domain — shows exactly which pages LLMs are linking to", {
|
|
292
|
+
entityId: entityIdSchema,
|
|
293
|
+
citedEntityUuid: z.string().describe("UUID of the cited domain to drill into"),
|
|
294
|
+
period: periodSchema,
|
|
295
|
+
startDate: startDateSchema,
|
|
296
|
+
endDate: endDateSchema,
|
|
297
|
+
models: modelsSchema,
|
|
298
|
+
limit: limitSchema,
|
|
299
|
+
offset: offsetSchema,
|
|
300
|
+
}, async (args) => {
|
|
301
|
+
try {
|
|
302
|
+
return jsonContent(await client.getCitationDetails({
|
|
303
|
+
...args,
|
|
304
|
+
limit: args.limit?.toString(),
|
|
305
|
+
offset: args.offset?.toString(),
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
return errorContent(err);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
// ────────────────────────────────────────────────────────────
|
|
313
|
+
// 15. GET COMPETITORS
|
|
314
|
+
// ────────────────────────────────────────────────────────────
|
|
315
|
+
server.tool("get_competitors", "Get competitor visibility leaderboard — see how your entity ranks against competitors in LLM mentions", {
|
|
316
|
+
entityId: entityIdSchema,
|
|
317
|
+
period: periodSchema,
|
|
318
|
+
startDate: startDateSchema,
|
|
319
|
+
endDate: endDateSchema,
|
|
320
|
+
models: modelsSchema,
|
|
321
|
+
competitorsOnly: z
|
|
322
|
+
.boolean()
|
|
323
|
+
.optional()
|
|
324
|
+
.describe("If true, only show competitors (exclude your entity)"),
|
|
325
|
+
limit: limitSchema,
|
|
326
|
+
offset: offsetSchema,
|
|
327
|
+
}, async (args) => {
|
|
328
|
+
try {
|
|
329
|
+
return jsonContent(await client.getCompetitors({
|
|
330
|
+
...args,
|
|
331
|
+
competitorsOnly: args.competitorsOnly?.toString(),
|
|
332
|
+
limit: args.limit?.toString(),
|
|
333
|
+
offset: args.offset?.toString(),
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
return errorContent(err);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
// ────────────────────────────────────────────────────────────
|
|
341
|
+
// 16. GET VISIBILITY TREND
|
|
342
|
+
// ────────────────────────────────────────────────────────────
|
|
343
|
+
server.tool("get_visibility_trend", "Get visibility trend over time — daily breakdown of mention rate, total responses, and visibility percentage", {
|
|
344
|
+
entityId: entityIdSchema,
|
|
345
|
+
period: periodSchema,
|
|
346
|
+
startDate: startDateSchema,
|
|
347
|
+
endDate: endDateSchema,
|
|
348
|
+
models: modelsSchema,
|
|
349
|
+
tags: tagsSchema,
|
|
350
|
+
promptIds: promptIdsSchema,
|
|
351
|
+
}, async (args) => {
|
|
352
|
+
try {
|
|
353
|
+
return jsonContent(await client.getVisibilityTrend(args));
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
return errorContent(err);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
// ────────────────────────────────────────────────────────────
|
|
360
|
+
// 17. GET SENTIMENT TREND
|
|
361
|
+
// ────────────────────────────────────────────────────────────
|
|
362
|
+
server.tool("get_sentiment_trend", "Get sentiment trend over time — daily breakdown of average sentiment score and mention count", {
|
|
363
|
+
entityId: entityIdSchema,
|
|
364
|
+
period: periodSchema,
|
|
365
|
+
startDate: startDateSchema,
|
|
366
|
+
endDate: endDateSchema,
|
|
367
|
+
models: modelsSchema,
|
|
368
|
+
tags: tagsSchema,
|
|
369
|
+
}, async (args) => {
|
|
370
|
+
try {
|
|
371
|
+
return jsonContent(await client.getSentimentTrend(args));
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
return errorContent(err);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
// ────────────────────────────────────────────────────────────
|
|
378
|
+
// 18. GET QUERY FANOUTS
|
|
379
|
+
// ────────────────────────────────────────────────────────────
|
|
380
|
+
server.tool("get_query_fanouts", "Get web search queries that LLMs performed while generating responses — shows what LLMs search for when answering about your entity", {
|
|
381
|
+
entityId: entityIdSchema,
|
|
382
|
+
period: periodSchema,
|
|
383
|
+
startDate: startDateSchema,
|
|
384
|
+
endDate: endDateSchema,
|
|
385
|
+
models: modelsSchema,
|
|
386
|
+
tags: tagsSchema,
|
|
387
|
+
limit: limitSchema,
|
|
388
|
+
offset: offsetSchema,
|
|
389
|
+
search: z.string().optional().describe("Full-text search filter on query text"),
|
|
390
|
+
}, async (args) => {
|
|
391
|
+
try {
|
|
392
|
+
return jsonContent(await client.getQueryFanouts({
|
|
393
|
+
...args,
|
|
394
|
+
limit: args.limit?.toString(),
|
|
395
|
+
offset: args.offset?.toString(),
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
return errorContent(err);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|