Sign In

@agentseo/mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agentseo/mcp-server - npm Package Compare versions

Comparing version
0.1.0
to
0.1.1
+107
-22
dist/index.js

@@ -10,2 +10,4 @@ #!/usr/bin/env node

const API_KEY = process.env.AGENTSEO_API_KEY;
const PROJECT_ID = process.env.AGENTSEO_PROJECT_ID;
const WORKFLOW_ID = process.env.AGENTSEO_WORKFLOW_ID;
if (!API_KEY) {

@@ -33,2 +35,4 @@ console.error("Error: AGENTSEO_API_KEY environment variable is required.");

"Content-Type": "application/json",
...(PROJECT_ID ? { "x-project-id": PROJECT_ID } : {}),
...(WORKFLOW_ID ? { "x-workflow-id": WORKFLOW_ID } : {}),
},

@@ -48,3 +52,5 @@ });

return {
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
content: [
{ type: "text", text: JSON.stringify(payload, null, 2) },
],
};

@@ -54,5 +60,17 @@ }

query: z.string().describe("The search query"),
limit: z.number().int().min(1).max(20).default(10).describe("Number of results"),
include_domains: z.array(z.string()).optional().describe("Restrict results to these domains"),
exclude_domains: z.array(z.string()).optional().describe("Exclude results from these domains"),
limit: z
.number()
.int()
.min(1)
.max(50)
.default(10)
.describe("Number of results"),
include_domains: z
.array(z.string())
.optional()
.describe("Restrict results to these domains"),
exclude_domains: z
.array(z.string())
.optional()
.describe("Exclude results from these domains"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),

@@ -68,3 +86,6 @@ }, async ({ query, limit, include_domains, exclude_domains, sync }) => {

url: z.string().url().describe("The URL to scrape"),
include_images: z.boolean().default(false).describe("Include image links in markdown"),
include_images: z
.boolean()
.default(false)
.describe("Include image links in markdown"),
}, async ({ url, include_images }) => {

@@ -74,3 +95,6 @@ const result = await callAgentSeoApi("/extract", {

});
const parsed = z.object({ content: z.string() }).passthrough().parse(result);
const parsed = z
.object({ content: z.string() })
.passthrough()
.parse(result);
return {

@@ -82,4 +106,10 @@ content: [{ type: "text", text: parsed.content }],

keyword: z.string().describe("Target keyword"),
location: z.string().default("United States").describe("Geographic location"),
device: z.enum(["desktop", "mobile"]).default("desktop").describe("Device type"),
location: z
.string()
.default("United States")
.describe("Geographic location"),
device: z
.enum(["desktop", "mobile"])
.default("desktop")
.describe("Device type"),
language: z.string().default("en").describe("Language code"),

@@ -107,7 +137,10 @@ sync: z.boolean().default(true).describe("Whether to run synchronously"),

server.tool("agentseo_local_audit_batch", "Queue up to 10 local audits in one request.", {
items: z.array(z.object({
items: z
.array(z.object({
domain: z.string(),
location: z.string(),
language: z.string().optional(),
})).max(10).describe("Audit items"),
}))
.max(10)
.describe("Audit items"),
}, async ({ items }) => {

@@ -122,5 +155,11 @@ const result = await callAgentSeoApi("/audit/local/batch", {

keyword: z.string().describe("Target keyword"),
location: z.string().default("United States").describe("Geographic location"),
location: z
.string()
.default("United States")
.describe("Geographic location"),
language: z.string().default("en").describe("Language code"),
scrape_depth: z.enum(["h1", "h2", "h3"]).default("h3").describe("How deep to analyze headings"),
scrape_depth: z
.enum(["h1", "h2", "h3"])
.default("h3")
.describe("How deep to analyze headings"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),

@@ -134,6 +173,9 @@ }, async ({ url, keyword, location, language, scrape_depth, sync }) => {

});
server.tool("agentseo_social_listen", "Run social listening analysis for a query.", {
server.tool("agentseo_social_listen", "Find web mentions for a query (web-wide discovery; not platform-native monitoring).", {
query: z.string().describe("Search query"),
platform: z.enum(["reddit", "twitter", "all"]).default("all").describe("Platform filter"),
limit: z.number().int().min(1).max(20).default(10).describe("Result limit"),
platform: z
.enum(["reddit", "twitter", "all"])
.default("all")
.describe("Platform filter"),
limit: z.number().int().min(1).max(50).default(10).describe("Result limit"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),

@@ -147,10 +189,33 @@ }, async ({ query, platform, limit, sync }) => {

});
// Preferred name; keep agentseo_social_listen for backward compatibility.
server.tool("agentseo_web_mentions", "Find web mentions for a query (web-wide discovery; not platform-native monitoring).", {
query: z.string().describe("Search query"),
platform: z
.enum(["reddit", "twitter", "all"])
.default("all")
.describe("Platform filter"),
limit: z.number().int().min(1).max(50).default(10).describe("Result limit"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
}, async ({ query, platform, limit, sync }) => {
const result = await callAgentSeoApi("/social/listen", {
data: { query, platform, limit },
params: { sync },
});
return jsonContent(result);
});
server.tool("agentseo_ai_overview_extract", "Extract AI Overview insights for a target keyword.", {
keyword: z.string().describe("Target keyword"),
location: z.string().default("United States").describe("Geographic location"),
location: z
.string()
.default("United States")
.describe("Geographic location"),
language: z.string().default("en").describe("Language code"),
target_domain: z
.string()
.optional()
.describe("Optional domain to check against the sampled citation candidates"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
}, async ({ keyword, location, language, sync }) => {
}, async ({ keyword, location, language, target_domain, sync }) => {
const result = await callAgentSeoApi("/ai-overview/extract", {
data: { keyword, location, language },
data: { keyword, location, language, target_domain },
params: { sync },

@@ -176,4 +241,13 @@ });

queries: z.array(z.string()).min(1).max(10).describe("Queries to check"),
limit_per_query: z.number().int().min(1).max(20).default(8).describe("Per-query result limit"),
platform: z.enum(["reddit", "twitter", "all"]).default("all").describe("Platform filter"),
limit_per_query: z
.number()
.int()
.min(1)
.max(20)
.default(8)
.describe("Per-query result limit"),
platform: z
.enum(["reddit", "twitter", "all"])
.default("all")
.describe("Platform filter"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),

@@ -190,4 +264,15 @@ }, async ({ brand, queries, limit_per_query, platform, sync }) => {

keyword: z.string().describe("Target keyword"),
lookback_days: z.number().int().min(3).max(180).default(30).describe("Lookback window"),
threshold: z.number().min(1).max(20).default(3).describe("Rank drop threshold"),
lookback_days: z
.number()
.int()
.min(3)
.max(180)
.default(30)
.describe("Lookback window"),
threshold: z
.number()
.min(1)
.max(20)
.default(3)
.describe("Rank drop threshold"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),

@@ -194,0 +279,0 @@ }, async ({ url, keyword, lookback_days, threshold, sync }) => {

+2
-2
{
"name": "@agentseo/mcp-server",
"version": "0.1.0",
"version": "0.1.1",
"type": "module",
"license": "MIT",
"bin": {
"agentseo-mcp-server": "./dist/index.js"
"agentseo-mcp-server": "dist/index.js"
},

@@ -9,0 +9,0 @@ "scripts": {

+85
-32
# @agentseo/mcp-server
The official Model Context Protocol (MCP) server for AgentSEO. Exposes SEO tools to Claude Desktop and other MCP clients.
The official Model Context Protocol (MCP) server for AgentSEO. Exposes SEO tools to Claude Desktop, Cursor, and other MCP clients.
## Installation
## Hosted remote MCP
This package is part of the AgentSEO monorepo.
AgentSEO also exposes a hosted remote MCP endpoint:
1. Build the server:
```bash
npm run build
```
`https://www.agentseo.dev/mcp`
2. Locate the build artifact:
`./dist/index.js`
Use a server-side API key with no allowed domains.
## Configuration
### Claude Code
The server runs over stdio and requires the following environment variables:
```bash
claude mcp add --transport http agentseo https://www.agentseo.dev/mcp \
--header "Authorization: Bearer sk_live_your_key" \
--header "x-project-id: client-alpha" \
--header "x-workflow-id: nightly-refresh"
```
- `AGENTSEO_API_KEY`: Your workspace API key.
- `AGENTSEO_API_URL`: (Optional) Defaults to `http://localhost:3000/api/v1`.
### Cursor
### Claude Desktop Example
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"agentseo": {
"url": "https://www.agentseo.dev/mcp",
"headers": {
"Authorization": "Bearer sk_live_your_key",
"x-project-id": "client-alpha",
"x-workflow-id": "nightly-refresh"
}
}
}
}
```
## Quickstart (Claude Desktop / Cursor)
### 1. Set your API key
```bash
export AGENTSEO_API_KEY="sk_live_your_key"
```
### 2. Add AgentSEO MCP server to your MCP config
For Claude Desktop, add this to `claude_desktop_config.json`.
For Cursor, add the same block in MCP settings:
```json

@@ -31,6 +56,9 @@ {

"agentseo": {
"command": "node",
"args": ["/absolute/path/to/packages/mcp-server/dist/index.js"],
"command": "npx",
"args": ["-y", "@agentseo/mcp-server"],
"env": {
"AGENTSEO_API_KEY": "sk_live_..."
"AGENTSEO_API_KEY": "sk_live_your_key",
"AGENTSEO_API_URL": "https://www.agentseo.dev/api/v1",
"AGENTSEO_PROJECT_ID": "client-alpha",
"AGENTSEO_WORKFLOW_ID": "nightly-refresh"
}

@@ -42,19 +70,44 @@ }

### 3. Restart your MCP client
After restart, ask Claude or Cursor:
`Run agentseo_local_audit for example.com in New York, NY`
## Environment Variables
- `AGENTSEO_API_KEY` (required): Your workspace API key.
- `AGENTSEO_API_URL` (optional): Defaults to `http://localhost:3000/api/v1`.
- For hosted API use `https://www.agentseo.dev/api/v1`.
- `AGENTSEO_PROJECT_ID` (optional): Default project attribution for all tool calls.
- `AGENTSEO_WORKFLOW_ID` (optional): Default workflow attribution for all tool calls.
## Available Tools
### `agentseo_search`
Perform a Google search.
- `query` (string): Search term.
- `limit` (number): Max results (default 10).
- `include_domains` (string[]): Domain whitelist.
- `agentseo_search`
- `agentseo_extract`
- `agentseo_analyze_serp`
- `agentseo_local_audit`
- `agentseo_local_audit_batch`
- `agentseo_content_gap`
- `agentseo_web_mentions` (preferred; alias: `agentseo_social_listen`)
- `agentseo_ai_overview_extract`
- `agentseo_local_visibility_track`
- `agentseo_llm_mentions_track`
- `agentseo_content_decay_detect`
- `agentseo_keyword_cluster_build`
- `agentseo_rank_track`
- `agentseo_rank_history`
- `agentseo_job_status`
### `agentseo_extract`
Extract content from a URL.
- `url` (string): Target URL.
- `include_images` (boolean): Keep image markdown.
`agentseo_ai_overview_extract` also accepts an optional `target_domain` when you want Claude or another MCP client to check whether your site appears in the sampled candidate set for an AI Overview query.
### `agentseo_analyze_serp`
Detailed SERP analysis.
- `keyword` (string): Target keyword.
- `location` (string): "United States", "United Kingdom", etc.
- `device` (string): "desktop" or "mobile".
## Local Run (Without Claude)
```bash
AGENTSEO_API_KEY="sk_live_your_key" npx -y @agentseo/mcp-server
```
If startup succeeds, you should see:
`AgentSEO MCP Server running on stdio`
+365
-269

@@ -11,27 +11,30 @@ #!/usr/bin/env node

const API_BASE_URL = process.env.AGENTSEO_API_URL || "http://localhost:3000/api/v1";
const API_BASE_URL =
process.env.AGENTSEO_API_URL || "http://localhost:3000/api/v1";
const API_KEY = process.env.AGENTSEO_API_KEY;
const PROJECT_ID = process.env.AGENTSEO_PROJECT_ID;
const WORKFLOW_ID = process.env.AGENTSEO_WORKFLOW_ID;
if (!API_KEY) {
console.error("Error: AGENTSEO_API_KEY environment variable is required.");
process.exit(1);
console.error("Error: AGENTSEO_API_KEY environment variable is required.");
process.exit(1);
}
const server = new McpServer({
name: "AgentSEO MCP Server",
version: "0.2.0",
name: "AgentSEO MCP Server",
version: "0.2.0",
});
interface AxiosLikeError {
isAxiosError?: boolean;
message?: string;
response?: {
data?: {
error?: unknown;
};
isAxiosError?: boolean;
message?: string;
response?: {
data?: {
error?: unknown;
};
};
}
function isAxiosLikeError(error: unknown): error is AxiosLikeError {
return typeof error === "object" && error !== null && "isAxiosError" in error;
return typeof error === "object" && error !== null && "isAxiosError" in error;
}

@@ -42,315 +45,408 @@

async function callAgentSeoApi<T = unknown>(
endpoint: string,
options: {
method?: ApiMethod;
data?: unknown;
params?: Record<string, string | number | boolean | undefined>;
} = {}
endpoint: string,
options: {
method?: ApiMethod;
data?: unknown;
params?: Record<string, string | number | boolean | undefined>;
} = {},
): Promise<T> {
const method = options.method ?? "POST";
try {
const response = await axios.request({
url: `${API_BASE_URL}${endpoint}`,
method,
data: options.data,
params: options.params,
headers: {
"x-api-key": API_KEY,
"Content-Type": "application/json",
},
});
return response.data as T;
} catch (error: unknown) {
if (isAxiosLikeError(error)) {
const errorData = error.response?.data?.error || error.message;
throw new Error(`AgentSEO API Error: ${JSON.stringify(errorData)}`);
}
throw error;
const method = options.method ?? "POST";
try {
const response = await axios.request({
url: `${API_BASE_URL}${endpoint}`,
method,
data: options.data,
params: options.params,
headers: {
"x-api-key": API_KEY,
"Content-Type": "application/json",
...(PROJECT_ID ? { "x-project-id": PROJECT_ID } : {}),
...(WORKFLOW_ID ? { "x-workflow-id": WORKFLOW_ID } : {}),
},
});
return response.data as T;
} catch (error: unknown) {
if (isAxiosLikeError(error)) {
const errorData = error.response?.data?.error || error.message;
throw new Error(`AgentSEO API Error: ${JSON.stringify(errorData)}`);
}
throw error;
}
}
function jsonContent(payload: unknown) {
return {
content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }],
};
return {
content: [
{ type: "text" as const, text: JSON.stringify(payload, null, 2) },
],
};
}
server.tool(
"agentseo_search",
"Perform a Google search with optional domain filters.",
{
query: z.string().describe("The search query"),
limit: z.number().int().min(1).max(20).default(10).describe("Number of results"),
include_domains: z.array(z.string()).optional().describe("Restrict results to these domains"),
exclude_domains: z.array(z.string()).optional().describe("Exclude results from these domains"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ query, limit, include_domains, exclude_domains, sync }) => {
const result = await callAgentSeoApi("/search", {
data: { query, limit, include_domains, exclude_domains },
params: { sync },
});
return jsonContent(result);
}
"agentseo_search",
"Perform a Google search with optional domain filters.",
{
query: z.string().describe("The search query"),
limit: z
.number()
.int()
.min(1)
.max(50)
.default(10)
.describe("Number of results"),
include_domains: z
.array(z.string())
.optional()
.describe("Restrict results to these domains"),
exclude_domains: z
.array(z.string())
.optional()
.describe("Exclude results from these domains"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ query, limit, include_domains, exclude_domains, sync }) => {
const result = await callAgentSeoApi("/search", {
data: { query, limit, include_domains, exclude_domains },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_extract",
"Extract content from a URL and convert to Markdown.",
{
url: z.string().url().describe("The URL to scrape"),
include_images: z.boolean().default(false).describe("Include image links in markdown"),
},
async ({ url, include_images }) => {
const result = await callAgentSeoApi("/extract", {
data: { url, format: "markdown", include_images },
});
const parsed = z.object({ content: z.string() }).passthrough().parse(result);
return {
content: [{ type: "text", text: parsed.content }],
};
}
"agentseo_extract",
"Extract content from a URL and convert to Markdown.",
{
url: z.string().url().describe("The URL to scrape"),
include_images: z
.boolean()
.default(false)
.describe("Include image links in markdown"),
},
async ({ url, include_images }) => {
const result = await callAgentSeoApi("/extract", {
data: { url, format: "markdown", include_images },
});
const parsed = z
.object({ content: z.string() })
.passthrough()
.parse(result);
return {
content: [{ type: "text", text: parsed.content }],
};
},
);
server.tool(
"agentseo_analyze_serp",
"Deep analysis of Search Engine Results Page (SERP) features and competitors.",
{
keyword: z.string().describe("Target keyword"),
location: z.string().default("United States").describe("Geographic location"),
device: z.enum(["desktop", "mobile"]).default("desktop").describe("Device type"),
language: z.string().default("en").describe("Language code"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keyword, location, device, language, sync }) => {
const result = await callAgentSeoApi("/analyze/serp", {
data: { keyword, location, device, language },
params: { sync },
});
return jsonContent(result);
}
"agentseo_analyze_serp",
"Deep analysis of Search Engine Results Page (SERP) features and competitors.",
{
keyword: z.string().describe("Target keyword"),
location: z
.string()
.default("United States")
.describe("Geographic location"),
device: z
.enum(["desktop", "mobile"])
.default("desktop")
.describe("Device type"),
language: z.string().default("en").describe("Language code"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keyword, location, device, language, sync }) => {
const result = await callAgentSeoApi("/analyze/serp", {
data: { keyword, location, device, language },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_local_audit",
"Run a local SEO audit for a domain and location.",
{
domain: z.string().describe("Domain or business name"),
location: z.string().describe("Target location"),
language: z.string().default("en").describe("Language code"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ domain, location, language, sync }) => {
const result = await callAgentSeoApi("/audit/local", {
data: { domain, location, language },
params: { sync },
});
return jsonContent(result);
}
"agentseo_local_audit",
"Run a local SEO audit for a domain and location.",
{
domain: z.string().describe("Domain or business name"),
location: z.string().describe("Target location"),
language: z.string().default("en").describe("Language code"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ domain, location, language, sync }) => {
const result = await callAgentSeoApi("/audit/local", {
data: { domain, location, language },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_local_audit_batch",
"Queue up to 10 local audits in one request.",
{
items: z.array(z.object({
domain: z.string(),
location: z.string(),
language: z.string().optional(),
})).max(10).describe("Audit items"),
},
async ({ items }) => {
const result = await callAgentSeoApi("/audit/local/batch", {
data: { items },
});
return jsonContent(result);
}
"agentseo_local_audit_batch",
"Queue up to 10 local audits in one request.",
{
items: z
.array(
z.object({
domain: z.string(),
location: z.string(),
language: z.string().optional(),
}),
)
.max(10)
.describe("Audit items"),
},
async ({ items }) => {
const result = await callAgentSeoApi("/audit/local/batch", {
data: { items },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_content_gap",
"Run content gap analysis for a URL and keyword.",
{
url: z.string().url().describe("Your URL"),
keyword: z.string().describe("Target keyword"),
location: z.string().default("United States").describe("Geographic location"),
language: z.string().default("en").describe("Language code"),
scrape_depth: z.enum(["h1", "h2", "h3"]).default("h3").describe("How deep to analyze headings"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ url, keyword, location, language, scrape_depth, sync }) => {
const result = await callAgentSeoApi("/content/gap", {
data: { url, keyword, location, language, scrape_depth },
params: { sync },
});
return jsonContent(result);
}
"agentseo_content_gap",
"Run content gap analysis for a URL and keyword.",
{
url: z.string().url().describe("Your URL"),
keyword: z.string().describe("Target keyword"),
location: z
.string()
.default("United States")
.describe("Geographic location"),
language: z.string().default("en").describe("Language code"),
scrape_depth: z
.enum(["h1", "h2", "h3"])
.default("h3")
.describe("How deep to analyze headings"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ url, keyword, location, language, scrape_depth, sync }) => {
const result = await callAgentSeoApi("/content/gap", {
data: { url, keyword, location, language, scrape_depth },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_social_listen",
"Run social listening analysis for a query.",
{
query: z.string().describe("Search query"),
platform: z.enum(["reddit", "twitter", "all"]).default("all").describe("Platform filter"),
limit: z.number().int().min(1).max(20).default(10).describe("Result limit"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ query, platform, limit, sync }) => {
const result = await callAgentSeoApi("/social/listen", {
data: { query, platform, limit },
params: { sync },
});
return jsonContent(result);
}
"agentseo_social_listen",
"Find web mentions for a query (web-wide discovery; not platform-native monitoring).",
{
query: z.string().describe("Search query"),
platform: z
.enum(["reddit", "twitter", "all"])
.default("all")
.describe("Platform filter"),
limit: z.number().int().min(1).max(50).default(10).describe("Result limit"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ query, platform, limit, sync }) => {
const result = await callAgentSeoApi("/social/listen", {
data: { query, platform, limit },
params: { sync },
});
return jsonContent(result);
},
);
// Preferred name; keep agentseo_social_listen for backward compatibility.
server.tool(
"agentseo_ai_overview_extract",
"Extract AI Overview insights for a target keyword.",
{
keyword: z.string().describe("Target keyword"),
location: z.string().default("United States").describe("Geographic location"),
language: z.string().default("en").describe("Language code"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keyword, location, language, sync }) => {
const result = await callAgentSeoApi("/ai-overview/extract", {
data: { keyword, location, language },
params: { sync },
});
return jsonContent(result);
}
"agentseo_web_mentions",
"Find web mentions for a query (web-wide discovery; not platform-native monitoring).",
{
query: z.string().describe("Search query"),
platform: z
.enum(["reddit", "twitter", "all"])
.default("all")
.describe("Platform filter"),
limit: z.number().int().min(1).max(50).default(10).describe("Result limit"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ query, platform, limit, sync }) => {
const result = await callAgentSeoApi("/social/listen", {
data: { query, platform, limit },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_local_visibility_track",
"Track local visibility for a domain across keywords and locations.",
{
domain: z.string().describe("Domain to track"),
keywords: z.array(z.string()).min(1).max(5).describe("Keywords"),
locations: z.array(z.string()).min(1).max(3).describe("Locations"),
language: z.string().default("en").describe("Language code"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ domain, keywords, locations, language, sync }) => {
const result = await callAgentSeoApi("/local-visibility/track", {
data: { domain, keywords, locations, language },
params: { sync },
});
return jsonContent(result);
}
"agentseo_ai_overview_extract",
"Extract AI Overview insights for a target keyword.",
{
keyword: z.string().describe("Target keyword"),
location: z
.string()
.default("United States")
.describe("Geographic location"),
language: z.string().default("en").describe("Language code"),
target_domain: z
.string()
.optional()
.describe(
"Optional domain to check against the sampled citation candidates",
),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keyword, location, language, target_domain, sync }) => {
const result = await callAgentSeoApi("/ai-overview/extract", {
data: { keyword, location, language, target_domain },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_llm_mentions_track",
"Track brand mentions across LLM/search-style results.",
{
brand: z.string().describe("Brand name"),
queries: z.array(z.string()).min(1).max(10).describe("Queries to check"),
limit_per_query: z.number().int().min(1).max(20).default(8).describe("Per-query result limit"),
platform: z.enum(["reddit", "twitter", "all"]).default("all").describe("Platform filter"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ brand, queries, limit_per_query, platform, sync }) => {
const result = await callAgentSeoApi("/llm-mentions/track", {
data: { brand, queries, limit_per_query, platform },
params: { sync },
});
return jsonContent(result);
}
"agentseo_local_visibility_track",
"Track local visibility for a domain across keywords and locations.",
{
domain: z.string().describe("Domain to track"),
keywords: z.array(z.string()).min(1).max(5).describe("Keywords"),
locations: z.array(z.string()).min(1).max(3).describe("Locations"),
language: z.string().default("en").describe("Language code"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ domain, keywords, locations, language, sync }) => {
const result = await callAgentSeoApi("/local-visibility/track", {
data: { domain, keywords, locations, language },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_content_decay_detect",
"Detect potential content decay for a URL and keyword.",
{
url: z.string().url().describe("Target URL"),
keyword: z.string().describe("Target keyword"),
lookback_days: z.number().int().min(3).max(180).default(30).describe("Lookback window"),
threshold: z.number().min(1).max(20).default(3).describe("Rank drop threshold"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ url, keyword, lookback_days, threshold, sync }) => {
const result = await callAgentSeoApi("/content-decay/detect", {
data: { url, keyword, lookback_days, threshold },
params: { sync },
});
return jsonContent(result);
}
"agentseo_llm_mentions_track",
"Track brand mentions across LLM/search-style results.",
{
brand: z.string().describe("Brand name"),
queries: z.array(z.string()).min(1).max(10).describe("Queries to check"),
limit_per_query: z
.number()
.int()
.min(1)
.max(20)
.default(8)
.describe("Per-query result limit"),
platform: z
.enum(["reddit", "twitter", "all"])
.default("all")
.describe("Platform filter"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ brand, queries, limit_per_query, platform, sync }) => {
const result = await callAgentSeoApi("/llm-mentions/track", {
data: { brand, queries, limit_per_query, platform },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_keyword_cluster_build",
"Build keyword clusters from an input keyword list.",
{
keywords: z.array(z.string()).min(1).max(200).describe("Keywords"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keywords, sync }) => {
const result = await callAgentSeoApi("/keyword-cluster/build", {
data: { keywords },
params: { sync },
});
return jsonContent(result);
}
"agentseo_content_decay_detect",
"Detect potential content decay for a URL and keyword.",
{
url: z.string().url().describe("Target URL"),
keyword: z.string().describe("Target keyword"),
lookback_days: z
.number()
.int()
.min(3)
.max(180)
.default(30)
.describe("Lookback window"),
threshold: z
.number()
.min(1)
.max(20)
.default(3)
.describe("Rank drop threshold"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ url, keyword, lookback_days, threshold, sync }) => {
const result = await callAgentSeoApi("/content-decay/detect", {
data: { url, keyword, lookback_days, threshold },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_rank_track",
"Queue a rank tracking check for a URL and keyword.",
{
keyword: z.string().describe("Keyword to track"),
url: z.string().url().describe("URL to track"),
location: z.string().default("United States").describe("Location"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keyword, url, location, sync }) => {
const result = await callAgentSeoApi("/rank/track", {
data: { keyword, url, location },
params: { sync },
});
return jsonContent(result);
}
"agentseo_keyword_cluster_build",
"Build keyword clusters from an input keyword list.",
{
keywords: z.array(z.string()).min(1).max(200).describe("Keywords"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keywords, sync }) => {
const result = await callAgentSeoApi("/keyword-cluster/build", {
data: { keywords },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_rank_history",
"Fetch historical rank data for a keyword and URL.",
{
keyword: z.string().describe("Keyword"),
url: z.string().url().describe("URL"),
},
async ({ keyword, url }) => {
const result = await callAgentSeoApi("/rank/track", {
method: "GET",
params: { keyword, url },
});
return jsonContent(result);
}
"agentseo_rank_track",
"Queue a rank tracking check for a URL and keyword.",
{
keyword: z.string().describe("Keyword to track"),
url: z.string().url().describe("URL to track"),
location: z.string().default("United States").describe("Location"),
sync: z.boolean().default(true).describe("Whether to run synchronously"),
},
async ({ keyword, url, location, sync }) => {
const result = await callAgentSeoApi("/rank/track", {
data: { keyword, url, location },
params: { sync },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_job_status",
"Get status/result for a previously queued job.",
{
job_id: z.string().describe("Job ID"),
},
async ({ job_id }) => {
const result = await callAgentSeoApi(`/jobs/${job_id}`, {
method: "GET",
});
return jsonContent(result);
}
"agentseo_rank_history",
"Fetch historical rank data for a keyword and URL.",
{
keyword: z.string().describe("Keyword"),
url: z.string().url().describe("URL"),
},
async ({ keyword, url }) => {
const result = await callAgentSeoApi("/rank/track", {
method: "GET",
params: { keyword, url },
});
return jsonContent(result);
},
);
server.tool(
"agentseo_job_status",
"Get status/result for a previously queued job.",
{
job_id: z.string().describe("Job ID"),
},
async ({ job_id }) => {
const result = await callAgentSeoApi(`/jobs/${job_id}`, {
method: "GET",
});
return jsonContent(result);
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("AgentSEO MCP Server running on stdio");
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("AgentSEO MCP Server running on stdio");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
console.error("Server error:", error);
process.exit(1);
});