@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/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geogenio/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for GeoGen LLM SEO tracking platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"geogen-mcp": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node build/index.js",
|
|
12
|
+
"dev": "tsc --watch"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
16
|
+
"zod": "^3.25.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.0.0",
|
|
20
|
+
"typescript": "^5.7.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoGen API Client
|
|
3
|
+
* Wraps all /v1 REST endpoints for use by MCP tool handlers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ApiClientConfig {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class GeoGenApiClient {
|
|
12
|
+
private baseUrl: string;
|
|
13
|
+
private apiKey: string;
|
|
14
|
+
|
|
15
|
+
constructor(config: ApiClientConfig) {
|
|
16
|
+
// Strip trailing slash from base URL
|
|
17
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
18
|
+
this.apiKey = config.apiKey;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private async request(path: string, options?: RequestInit): Promise<any> {
|
|
22
|
+
const url = `${this.baseUrl}${path}`;
|
|
23
|
+
const response = await fetch(url, {
|
|
24
|
+
...options,
|
|
25
|
+
headers: {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
28
|
+
...options?.headers,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const errorMsg = data?.error || data?.message || `HTTP ${response.status}`;
|
|
36
|
+
throw new Error(errorMsg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return data;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private buildQuery(params: Record<string, string | undefined>): string {
|
|
43
|
+
const entries = Object.entries(params).filter(
|
|
44
|
+
(entry): entry is [string, string] => entry[1] !== undefined && entry[1] !== ""
|
|
45
|
+
);
|
|
46
|
+
if (entries.length === 0) return "";
|
|
47
|
+
return "?" + new URLSearchParams(entries).toString();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Entities ──────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
async getEntities(): Promise<any> {
|
|
53
|
+
return this.request("/v1/entities");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async createEntity(body: {
|
|
57
|
+
name: string;
|
|
58
|
+
domain: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
language?: string;
|
|
61
|
+
geolocation?: string;
|
|
62
|
+
models?: string[];
|
|
63
|
+
trackingType?: string;
|
|
64
|
+
generatePrompts?: boolean;
|
|
65
|
+
startTracking?: boolean;
|
|
66
|
+
}): Promise<any> {
|
|
67
|
+
return this.request("/v1/entities", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
body: JSON.stringify(body),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Prompts ───────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
async getEntityPrompts(params: {
|
|
76
|
+
entityId: string;
|
|
77
|
+
period?: string;
|
|
78
|
+
startDate?: string;
|
|
79
|
+
endDate?: string;
|
|
80
|
+
tags?: string;
|
|
81
|
+
}): Promise<any> {
|
|
82
|
+
const query = this.buildQuery(params);
|
|
83
|
+
return this.request(`/v1/entities/prompts${query}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async addPrompts(body: {
|
|
87
|
+
entityId: string;
|
|
88
|
+
prompt?: string;
|
|
89
|
+
prompts?: Array<{ prompt: string; language?: string; geolocation?: string }>;
|
|
90
|
+
language?: string;
|
|
91
|
+
geolocation?: string;
|
|
92
|
+
}): Promise<any> {
|
|
93
|
+
return this.request("/v1/entities/addprompt", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
body: JSON.stringify(body),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async deletePrompt(promptId: string): Promise<any> {
|
|
100
|
+
const query = this.buildQuery({ promptId });
|
|
101
|
+
return this.request(`/v1/entities/deleteprompt${query}`, {
|
|
102
|
+
method: "DELETE",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Workspace ─────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
async getWorkspace(): Promise<any> {
|
|
109
|
+
return this.request("/v1/workspace");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getWorkspaceMembers(): Promise<any> {
|
|
113
|
+
return this.request("/v1/workspace/members");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getWorkspaceTags(): Promise<any> {
|
|
117
|
+
return this.request("/v1/workspace/tags");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Models ────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
async getModels(): Promise<any> {
|
|
123
|
+
return this.request("/v1/models");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Responses ─────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
async getResponses(params: {
|
|
129
|
+
entityId: string;
|
|
130
|
+
period?: string;
|
|
131
|
+
startDate?: string;
|
|
132
|
+
endDate?: string;
|
|
133
|
+
models?: string;
|
|
134
|
+
tags?: string;
|
|
135
|
+
promptIds?: string;
|
|
136
|
+
mentionStatus?: string;
|
|
137
|
+
limit?: string;
|
|
138
|
+
offset?: string;
|
|
139
|
+
}): Promise<any> {
|
|
140
|
+
const query = this.buildQuery(params);
|
|
141
|
+
return this.request(`/v1/responses${query}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getResponseDetails(params: {
|
|
145
|
+
responseId: string;
|
|
146
|
+
entityId: string;
|
|
147
|
+
}): Promise<any> {
|
|
148
|
+
const query = this.buildQuery(params);
|
|
149
|
+
return this.request(`/v1/responses/details${query}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Citations ─────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
async getCitations(params: {
|
|
155
|
+
entityId: string;
|
|
156
|
+
period?: string;
|
|
157
|
+
startDate?: string;
|
|
158
|
+
endDate?: string;
|
|
159
|
+
models?: string;
|
|
160
|
+
tags?: string;
|
|
161
|
+
promptIds?: string;
|
|
162
|
+
limit?: string;
|
|
163
|
+
offset?: string;
|
|
164
|
+
}): Promise<any> {
|
|
165
|
+
const query = this.buildQuery(params);
|
|
166
|
+
return this.request(`/v1/entities/citations${query}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async getCitationsTrend(params: {
|
|
170
|
+
entityId: string;
|
|
171
|
+
period?: string;
|
|
172
|
+
startDate?: string;
|
|
173
|
+
endDate?: string;
|
|
174
|
+
models?: string;
|
|
175
|
+
tags?: string;
|
|
176
|
+
promptIds?: string;
|
|
177
|
+
topN?: string;
|
|
178
|
+
}): Promise<any> {
|
|
179
|
+
const query = this.buildQuery(params);
|
|
180
|
+
return this.request(`/v1/trends/citations${query}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async getCitationDetails(params: {
|
|
184
|
+
entityId: string;
|
|
185
|
+
citedEntityUuid: string;
|
|
186
|
+
period?: string;
|
|
187
|
+
startDate?: string;
|
|
188
|
+
endDate?: string;
|
|
189
|
+
models?: string;
|
|
190
|
+
limit?: string;
|
|
191
|
+
offset?: string;
|
|
192
|
+
}): Promise<any> {
|
|
193
|
+
const query = this.buildQuery(params);
|
|
194
|
+
return this.request(`/v1/entities/citations/details${query}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Competitors ───────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
async getCompetitors(params: {
|
|
200
|
+
entityId: string;
|
|
201
|
+
period?: string;
|
|
202
|
+
startDate?: string;
|
|
203
|
+
endDate?: string;
|
|
204
|
+
models?: string;
|
|
205
|
+
competitorsOnly?: string;
|
|
206
|
+
limit?: string;
|
|
207
|
+
offset?: string;
|
|
208
|
+
}): Promise<any> {
|
|
209
|
+
const query = this.buildQuery(params);
|
|
210
|
+
return this.request(`/v1/competitors${query}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Trends ────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
async getVisibilityTrend(params: {
|
|
216
|
+
entityId: string;
|
|
217
|
+
period?: string;
|
|
218
|
+
startDate?: string;
|
|
219
|
+
endDate?: string;
|
|
220
|
+
models?: string;
|
|
221
|
+
tags?: string;
|
|
222
|
+
promptIds?: string;
|
|
223
|
+
}): Promise<any> {
|
|
224
|
+
const query = this.buildQuery(params);
|
|
225
|
+
return this.request(`/v1/trends/visibility${query}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async getSentimentTrend(params: {
|
|
229
|
+
entityId: string;
|
|
230
|
+
period?: string;
|
|
231
|
+
startDate?: string;
|
|
232
|
+
endDate?: string;
|
|
233
|
+
models?: string;
|
|
234
|
+
tags?: string;
|
|
235
|
+
}): Promise<any> {
|
|
236
|
+
const query = this.buildQuery(params);
|
|
237
|
+
return this.request(`/v1/trends/sentiment${query}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Query Fanouts ─────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
async getQueryFanouts(params: {
|
|
243
|
+
entityId: string;
|
|
244
|
+
period?: string;
|
|
245
|
+
startDate?: string;
|
|
246
|
+
endDate?: string;
|
|
247
|
+
models?: string;
|
|
248
|
+
tags?: string;
|
|
249
|
+
limit?: string;
|
|
250
|
+
offset?: string;
|
|
251
|
+
search?: string;
|
|
252
|
+
}): Promise<any> {
|
|
253
|
+
const query = this.buildQuery(params);
|
|
254
|
+
return this.request(`/v1/query-fanouts${query}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GeoGen MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Exposes the GeoGen LLM SEO tracking API as MCP tools
|
|
7
|
+
* for AI assistants (Claude Desktop, Cursor, Windsurf, Claude Code, etc.)
|
|
8
|
+
*
|
|
9
|
+
* Configuration via environment variables:
|
|
10
|
+
* GEOGEN_API_KEY - Your GeoGen workspace API key (required)
|
|
11
|
+
* GEOGEN_BASE_URL - Your Convex site URL (required)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
|
+
import { GeoGenApiClient } from "./api-client.js";
|
|
17
|
+
import { registerTools } from "./tools.js";
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const apiKey = process.env.GEOGEN_API_KEY;
|
|
21
|
+
const baseUrl = process.env.GEOGEN_BASE_URL;
|
|
22
|
+
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
console.error("Error: GEOGEN_API_KEY environment variable is required.");
|
|
25
|
+
console.error("Set it to your GeoGen workspace API key.");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!baseUrl) {
|
|
30
|
+
console.error("Error: GEOGEN_BASE_URL environment variable is required.");
|
|
31
|
+
console.error("Set it to your Convex site URL (e.g. https://your-app.convex.site).");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create the API client
|
|
36
|
+
const client = new GeoGenApiClient({ baseUrl, apiKey });
|
|
37
|
+
|
|
38
|
+
// Create the MCP server
|
|
39
|
+
const server = new McpServer({
|
|
40
|
+
name: "geogen",
|
|
41
|
+
version: "1.0.0",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Register all tools
|
|
45
|
+
registerTools(server, client);
|
|
46
|
+
|
|
47
|
+
// Connect via stdio transport and start
|
|
48
|
+
const transport = new StdioServerTransport();
|
|
49
|
+
server.connect(transport).then(() => {
|
|
50
|
+
console.error("GeoGen MCP Server running on stdio");
|
|
51
|
+
}).catch((error) => {
|
|
52
|
+
console.error("Failed to start GeoGen MCP Server:", error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main();
|