#
tokens: 49510/50000 74/110 files (page 1/4)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 4. Use http://codebase.md/dataforseo/mcp-server-typescript?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .gitignore
├── Dockerfile
├── field-config.example.json
├── LICENSE
├── package.json
├── README.md
├── scripts
│   └── generate-worker-version.cjs
├── src
│   ├── core
│   │   ├── client
│   │   │   └── dataforseo.client.ts
│   │   ├── config
│   │   │   ├── field-configuration.ts
│   │   │   ├── global.tool.ts
│   │   │   └── modules.config.ts
│   │   ├── modules
│   │   │   ├── ai-optimization
│   │   │   │   ├── ai-optimization-api-module.ts
│   │   │   │   └── tools
│   │   │   │       └── keyword-data
│   │   │   │           ├── ai-optimization-keyword-data-locations-and-languages.ts
│   │   │   │           └── ai-optimization-keyword-data-search-volume.ts
│   │   │   ├── backlinks
│   │   │   │   ├── backlinks-api.module.ts
│   │   │   │   ├── backlinks.prompt.ts
│   │   │   │   └── tools
│   │   │   │       ├── backlinks-anchor.tool.ts
│   │   │   │       ├── backlinks-backlinks.tool.ts
│   │   │   │       ├── backlinks-bulk-backlinks.tool.ts
│   │   │   │       ├── backlinks-bulk-new-lost-backlinks.tool.ts
│   │   │   │       ├── backlinks-bulk-new-lost-referring-domains.tool.ts
│   │   │   │       ├── backlinks-bulk-pages-summary.ts
│   │   │   │       ├── backlinks-bulk-ranks.tool.ts
│   │   │   │       ├── backlinks-bulk-referring-domains.tool.ts
│   │   │   │       ├── backlinks-bulk-spam-score.tool.ts
│   │   │   │       ├── backlinks-bulk-spam-score.ts
│   │   │   │       ├── backlinks-competitors.tool.ts
│   │   │   │       ├── backlinks-domain-intersection.tool.ts
│   │   │   │       ├── backlinks-domain-pages-summary.tool.ts
│   │   │   │       ├── backlinks-domain-pages.tool.ts
│   │   │   │       ├── backlinks-filters.tool.ts
│   │   │   │       ├── backlinks-page-intersection.tool.ts
│   │   │   │       ├── backlinks-referring-domains.tool.ts
│   │   │   │       ├── backlinks-referring-networks.tool.ts
│   │   │   │       ├── backlinks-summary.tool.ts
│   │   │   │       ├── backlinks-timeseries-new-lost-summary.tool.ts
│   │   │   │       └── backlinks-timeseries-summary.tool.ts
│   │   │   ├── base.module.ts
│   │   │   ├── base.tool.ts
│   │   │   ├── business-data-api
│   │   │   │   ├── business-data-api.module.ts
│   │   │   │   └── tools
│   │   │   │       └── listings
│   │   │   │           ├── business-listings-filters.tool.ts
│   │   │   │           └── business-listings-search.tool.ts
│   │   │   ├── content-analysis
│   │   │   │   ├── content-analysis-api.module.ts
│   │   │   │   └── tools
│   │   │   │       ├── content-analysis-phrase-trends.ts
│   │   │   │       ├── content-analysis-search.tool.ts
│   │   │   │       └── content-analysis-summary.ts
│   │   │   ├── dataforseo-labs
│   │   │   │   ├── dataforseo-labs-api.module.ts
│   │   │   │   ├── dataforseo-labs.prompts.ts
│   │   │   │   └── tools
│   │   │   │       ├── google
│   │   │   │       │   ├── competitor-research
│   │   │   │       │   │   ├── google-bulk-traffic-estimation.tool.ts
│   │   │   │       │   │   ├── google-domain-competitors.tool.ts
│   │   │   │       │   │   ├── google-domain-intersection.tool.ts
│   │   │   │       │   │   ├── google-domain-rank-overview.tool.ts
│   │   │   │       │   │   ├── google-historical-domain-rank-overview.tool.ts
│   │   │   │       │   │   ├── google-historical-serp.ts
│   │   │   │       │   │   ├── google-page-intersection.tool.ts
│   │   │   │       │   │   ├── google-ranked-keywords.tool.ts
│   │   │   │       │   │   ├── google-relevant-pages.ts
│   │   │   │       │   │   ├── google-serp-competitors.tool.ts
│   │   │   │       │   │   └── google-subdomains.ts
│   │   │   │       │   ├── keyword-research
│   │   │   │       │   │   ├── google-bulk-keyword-difficulty.tool.ts
│   │   │   │       │   │   ├── google-historical-keyword-data.tool.ts
│   │   │   │       │   │   ├── google-keyword-overview.tool.ts
│   │   │   │       │   │   ├── google-keywords-for-site.tool.ts
│   │   │   │       │   │   ├── google-keywords-ideas.tool.ts
│   │   │   │       │   │   ├── google-keywords-suggestions.tool.ts
│   │   │   │       │   │   ├── google-related-keywords.tool.ts
│   │   │   │       │   │   └── google-search-intent.tool.ts
│   │   │   │       │   └── market-analysis
│   │   │   │       │       └── google-top-searches.tool.ts
│   │   │   │       └── labs-filters.tool.ts
│   │   │   ├── domain-analytics
│   │   │   │   ├── domain-analytics-api.module.ts
│   │   │   │   └── tools
│   │   │   │       ├── technologies
│   │   │   │       │   ├── domain-technologies-filters.tool.ts
│   │   │   │       │   └── domain-technologies.tool.ts
│   │   │   │       └── whois
│   │   │   │           ├── whois-filters.tool.ts
│   │   │   │           └── whois-overview.tool.ts
│   │   │   ├── keywords-data
│   │   │   │   ├── keywords-data-api.module.ts
│   │   │   │   └── tools
│   │   │   │       ├── dataforseo-trends
│   │   │   │       │   ├── dataforseo-trends-demography.tool.ts
│   │   │   │       │   ├── dataforseo-trends-explore.tool.ts
│   │   │   │       │   └── dataforseo-trends-subregion-interests.tool.ts
│   │   │   │       ├── google-ads
│   │   │   │       │   └── google-ads-search-volume.tool.ts
│   │   │   │       └── google-trends
│   │   │   │           ├── google-trends-categories.tool.ts
│   │   │   │           └── google-trends-explore.tool.ts
│   │   │   ├── onpage
│   │   │   │   ├── onpage-api.module.ts
│   │   │   │   ├── onpage.prompt.ts
│   │   │   │   └── tools
│   │   │   │       ├── content-parsing.tool.ts
│   │   │   │       ├── instant-pages.tool.ts
│   │   │   │       └── lighthouse.tool.ts
│   │   │   ├── prompt-definition.ts
│   │   │   └── serp
│   │   │       ├── serp-api.module.ts
│   │   │       ├── serp.prompt.ts
│   │   │       └── tools
│   │   │           ├── serp-organic-live-advanced.tool.ts
│   │   │           ├── serp-organic-locations-list.tool.ts
│   │   │           ├── serp-youtube-locations-list.tool.ts
│   │   │           ├── serp-youtube-organic-live-advanced.tool.ts
│   │   │           ├── serp-youtube-video-comments-live-advanced-tool.ts
│   │   │           ├── serp-youtube-video-info-live-advanced.tool.ts
│   │   │           └── serp-youtube-video-subtitles-live-advanced-tool.ts
│   │   └── utils
│   │       ├── field-filter.ts
│   │       ├── map-array-to-numbered-keys.ts
│   │       ├── module-loader.ts
│   │       └── version.ts
│   ├── main
│   │   ├── cli.ts
│   │   ├── index-http.ts
│   │   ├── index-sse-http.ts
│   │   ├── index.ts
│   │   ├── init-mcp-server.ts
│   │   └── test.ts
│   └── worker
│       ├── index-worker.ts
│       └── worker-configuration.d.ts
├── tsconfig.json
├── tsconfig.worker.json
└── wrangler.jsonc
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | build/*
 2 | node_modules/*
 3 | .wrangler/*
 4 | src/worker/version.worker.ts
 5 | instruct.txt
 6 | .env
 7 | src/main/test2.ts
 8 | .vscode/launch.json
 9 | .vscode/tasks.json
10 | package-lock.json
11 | 
12 | 
```

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
 1 | # Node modules
 2 | node_modules
 3 | npm-debug.log*
 4 | 
 5 | # Build output (will be generated during build)
 6 | build
 7 | 
 8 | # Git
 9 | .git
10 | .gitignore
11 | 
12 | # Documentation
13 | README.md
14 | *.md
15 | 
16 | # IDE files
17 | .vscode
18 | .idea
19 | *.swp
20 | *.swo
21 | 
22 | # OS files
23 | .DS_Store
24 | Thumbs.db
25 | 
26 | # Environment files
27 | .env
28 | .env.local
29 | .env.*.local
30 | 
31 | # Logs
32 | logs
33 | *.log
34 | 
35 | # Test files
36 | test
37 | tests
38 | *.test.*
39 | *.spec.*
40 | 
41 | # Coverage
42 | coverage
43 | .nyc_output
44 | 
45 | # Temporary files
46 | .tmp
47 | tmp
48 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # DataForSEO MCP Server
  2 | 
  3 | Model Context Protocol (MCP) server implementation for DataForSEO, enabling AI assistants to interact with selected DataForSEO APIs and obtain SEO data through a standardized interface. 
  4 | 
  5 | ## Features
  6 | 
  7 | - **AI_OPTIMIZATION API**: provides data for keyword discovery, conversational optimization, and real-time LLM benchmarking; 
  8 | - **SERP API**: real-time Search Engine Results Page (SERP) data for Google, Bing, and Yahoo;
  9 | - **KEYWORDS_DATA API**: keyword research and clickstream data, including search volume, cost-per-click, and other metrics;   
 10 | - **ONPAGE API**: allows crawling websites and webpages according to customizable parameters to obtain on-page SEO performance metrics; 
 11 | - **DATAFORSEO LABS API**: data on keywords, SERPs, and domains based on DataForSEO's in-house databases and proprietary algorithms;
 12 | - **BACKLINKS API**: comprehensive backlink analysis including referring domains, anchor text distribution, and link quality metrics;
 13 | - **BUSINESS DATA API**: publicly available data on any business entity;
 14 | - **DOMAIN ANALYTICS API**: data on website traffic, technologies, and Whois details;
 15 | - **CONTENT ANALYSIS API**: robust source of data for brand monitoring, sentiment analysis, and citation management;
 16 | 
 17 | ## Prerequisites
 18 | 
 19 | - Node.js (v14 or higher)
 20 | - DataForSEO API credentials (API login and password)
 21 | 
 22 | ## Installation
 23 | 
 24 | 1. Clone the repository:
 25 | ```bash
 26 | git clone https://github.com/dataforseo/mcp-server-typescript
 27 | cd mcp-server-typescript
 28 | ```
 29 | 
 30 | 2. Install dependencies:
 31 | ```bash
 32 | npm install
 33 | ```
 34 | 
 35 | 3. Set up environment variables:
 36 | ```bash
 37 | # Required
 38 | export DATAFORSEO_USERNAME=your_username
 39 | export DATAFORSEO_PASSWORD=your_password
 40 | 
 41 | # Optional: specify which modules to enable (comma-separated)
 42 | # If not set, all modules will be enabled
 43 | export ENABLED_MODULES="SERP,KEYWORDS_DATA,ONPAGE,DATAFORSEO_LABS,BACKLINKS,BUSINESS_DATA,DOMAIN_ANALYTICS"
 44 | 
 45 | # Optional: specify which prompts in enabled modules are enable too (prompts names, comma-separated)
 46 | # If not set, all prompts from enabled modules will be enabled
 47 | export ENABLED_PROMPTS="top_3_google_result_domains,top_5_serp_paid_and_organic"
 48 | 
 49 | # Optional: enable full API responses
 50 | # If not set or set to false, the server will filter and transform API responses to a more concise format
 51 | # If set to true, the server will return the full, unmodified API responses
 52 | export DATAFORSEO_FULL_RESPONSE="false"
 53 | 
 54 | # Optional: enable simple filter schema
 55 | # If set to true, a simplified version of the filters schema will be used.
 56 | # This is required for ChatGPT APIs or other LLMs that cannot handle nested structures.
 57 | export DATAFORSEO_SIMPLE_FILTER="false"
 58 | ```
 59 | 
 60 | ## Installation as an NPM Package
 61 | 
 62 | You can install the package globally:
 63 | 
 64 | ```bash
 65 | npm install -g dataforseo-mcp-server
 66 | ```
 67 | 
 68 | Or run it directly without installation:
 69 | 
 70 | ```bash
 71 | npx dataforseo-mcp-server
 72 | ```
 73 | 
 74 | Remember to set environment variables before running the command:
 75 | 
 76 | ```bash
 77 | # Required environment variables
 78 | export DATAFORSEO_USERNAME=your_username
 79 | export DATAFORSEO_PASSWORD=your_password
 80 | 
 81 | # Run with npx
 82 | npx dataforseo-mcp-server
 83 | ```
 84 | 
 85 | ## Building and Running
 86 | 
 87 | Build the project:
 88 | ```bash
 89 | npm run build
 90 | ```
 91 | 
 92 | Run the server:
 93 | ```bash
 94 | # Start local server (direct MCP communication)
 95 | npx dataforseo-mcp-server
 96 | 
 97 | # Start HTTP server
 98 | npx dataforseo-mcp-server http
 99 | ```
100 | 
101 | ## HTTP Server Configuration
102 | 
103 | The server runs on port 3000 by default and supports both Basic Authentication and environment variable-based authentication.
104 | 
105 | To start the HTTP server, run:
106 | ```bash
107 | npm run http
108 | ```
109 | 
110 | ### Authentication Methods
111 | 
112 | 1. **Basic Authentication**
113 |    - Send requests with Basic Auth header:
114 |    ```
115 |    Authorization: Basic <base64-encoded-credentials>
116 |    ```
117 |    - Credentials format: `username:password`
118 | 
119 | 2. **Environment Variables**
120 |    - If no Basic Auth is provided, the server will use credentials from environment variables:
121 |    ```bash
122 |    export DATAFORSEO_USERNAME=your_username
123 |    export DATAFORSEO_PASSWORD=your_password
124 |    # Optional
125 |    export DATAFORSEO_SIMPLE_FILTER="false"
126 |    export DATAFORSEO_FULL_RESPONSE="true"
127 |    ```
128 | 
129 | ## Cloudflare Worker Deployment
130 | 
131 | The DataForSEO MCP Server can be deployed as a Cloudflare Worker for serverless, edge-distributed access to DataForSEO APIs.
132 | 
133 | ### Worker Features
134 | 
135 | - **Edge Distribution**: Deploy globally across Cloudflare's edge network
136 | - **Serverless**: No server management required
137 | - **Auto-scaling**: Handles traffic spikes automatically
138 | - **MCP Protocol Support**: Compatible with both Streamable HTTP and SSE transports
139 | - **Environment Variables**: Secure credential management through Cloudflare dashboard
140 | 
141 | ### Quick Start
142 | 
143 | 1. **Install Wrangler CLI**:
144 |    ```bash
145 |    npm install -g wrangler
146 |    ```
147 | 
148 | 2. **Configure Worker**:
149 |    ```bash
150 |    # Login to Cloudflare
151 |    wrangler login
152 |    
153 |    # Set environment variables
154 |    wrangler secret put DATAFORSEO_USERNAME
155 |    wrangler secret put DATAFORSEO_PASSWORD
156 |    ```
157 | 
158 | 3. **Deploy Worker**:
159 |    ```bash
160 |    # Build and deploy
161 |    npm run build
162 |    wrangler deploy --main build/index-worker.js
163 |    ```
164 | 
165 | ### Configuration
166 | 
167 | The worker uses the same environment variables as the standard server:
168 | 
169 | - `DATAFORSEO_USERNAME`: Your DataForSEO username
170 | - `DATAFORSEO_PASSWORD`: Your DataForSEO password  
171 | - `ENABLED_MODULES`: Comma-separated list of modules to enable
172 | - `ENABLED_PROMPTS`: Comma-separated list of prompt names to enable 
173 | - `DATAFORSEO_FULL_RESPONSE`: Set to "true" for full API responses
174 | 
175 | ### Worker Endpoints
176 | 
177 | Once deployed, your worker will be available at `https://your-worker.your-subdomain.workers.dev/` with the following endpoints:
178 | 
179 | - **POST /mcp**: Streamable HTTP transport (recommended)
180 | - **GET /sse**: SSE connection establishment (deprecated)
181 | - **POST /messages**: SSE message handling (deprecated)
182 | - **GET /health**: Health check endpoint
183 | - **GET /**: API documentation page
184 | 
185 | ### Advanced Configuration
186 | 
187 | Edit `wrangler.jsonc` to customize your deployment:
188 | 
189 | ```jsonc
190 | {
191 |   "name": "dataforseo-mcp-worker",
192 |   "main": "build/index-worker.js",
193 |   "compatibility_date": "2025-07-10",
194 |   "compatibility_flags": ["nodejs_compat"],
195 |   "vars": {
196 |     "ENABLED_MODULES": "SERP,KEYWORDS_DATA,ONPAGE,DATAFORSEO_LABS",
197 |     "ENABLED_PROMPTS":"top_3_google_result_domains,top_5_serp_paid_and_organic"
198 |   }
199 | }
200 | ```
201 | 
202 | ### Usage with Claude
203 | 
204 | After deployment, configure Claude to use your worker:
205 | 
206 | ```json
207 | {
208 |   "name": "DataForSEO",
209 |   "description": "Access DataForSEO APIs via Cloudflare Worker",
210 |   "transport": {
211 |     "type": "http",
212 |     "baseUrl": "https://your-worker.your-subdomain.workers.dev/mcp"
213 |   }
214 | }
215 | ```
216 |    
217 | ## Available Modules
218 | 
219 | The following modules are available to be enabled/disabled:
220 | 
221 | - `AI_OPTIMIZATION`: provides data for keyword discovery, conversational optimization, and real-time LLM benchmarking;
222 | - `SERP`: real-time SERP data for Google, Bing, and Yahoo;
223 | - `KEYWORDS_DATA`: keyword research and clickstream data;
224 | - `ONPAGE`: crawl websites and webpages to obtain on-page SEO performance metrics;
225 | - `DATAFORSEO_LABS`: data on keywords, SERPs, and domains based on DataForSEO's databases and algorithms;
226 | - `BACKLINKS`: data on inbound links, referring domains and referring pages for any domain, subdomain, or webpage;
227 | - `BUSINESS_DATA`: based on business reviews and business information publicly shared on the following platforms: Google, Trustpilot, Tripadvisor;
228 | - `DOMAIN_ANALYTICS`: helps identify all possible technologies used for building websites and offers Whois data;
229 | - `CONTENT_ANALYSIS`: help you discover citations of the target keyword or brand and analyze the sentiments around it;
230 | 
231 | ## Adding New Tools/Modules
232 | 
233 | ### Module Structure
234 | 
235 | Each module corresponds to a specific DataForSEO API:
236 | - `AI_OPTIMIZATION`: [AI Optimization API](https://docs.dataforseo.com/v3/ai_optimization/overview)
237 | - `SERP` module → [SERP API](https://docs.dataforseo.com/v3/serp/overview)
238 | - `KEYWORDS_DATA` module → [Keywords Data API](https://docs.dataforseo.com/v3/keywords_data/overview)
239 | - `ONPAGE` module → [OnPage API](https://docs.dataforseo.com/v3/on_page/overview)
240 | - `DATAFORSEO_LABS` module → [DataForSEO Labs API](https://docs.dataforseo.com/v3/dataforseo_labs/overview)
241 | - `BACKLINKS`: module → [Backlinks API](https://docs.dataforseo.com/v3/backlinks/overview)
242 | - `BUSINESS_DATA`: module → [Business Data API](https://docs.dataforseo.com/v3/business_data/overview)
243 | - `DOMAIN_ANALYTICS`: module → [Domain Analytics API](https://docs.dataforseo.com/v3/domain_analytics/overview)
244 | - `CONTENT_ANALYSIS`: module → [Content Analysis API](https://docs.dataforseo.com/v3/content_analysis/overview)
245 | 
246 | ### Implementation Options
247 | 
248 | You can either:
249 | 1. Add a new tool to an existing module
250 | 2. Create a completely new module
251 | 
252 | ### Adding a New Tool
253 | 
254 | Here's how to add a new tool to any new or pre-existing module:
255 | 
256 | ```typescript
257 | // src/code/modules/your-module/tools/your-tool.tool.ts
258 | import { BaseTool } from '../../base.tool';
259 | import { DataForSEOClient } from '../../../client/dataforseo.client';
260 | import { z } from 'zod';
261 | 
262 | export class YourTool extends BaseTool {
263 |   constructor(private client: DataForSEOClient) {
264 |     super(client);
265 |     // DataForSEO API returns extensive data with many fields, which can be overwhelming
266 |     // for AI agents to process. We select only the most relevant fields to ensure
267 |     // efficient and focused responses.
268 |     this.fields = [
269 |       'title',           // Example: Include the title field
270 |       'description',     // Example: Include the description field
271 |       'url',            // Example: Include the URL field
272 |       // Add more fields as needed
273 |     ];
274 |   }
275 | 
276 |   getName() {
277 |     return 'your-tool-name';
278 |   }
279 | 
280 |   getDescription() {
281 |     return 'Description of what your tool does';
282 |   }
283 | 
284 |   getParams(): z.ZodRawShape {
285 |     return {
286 |       // Required parameters
287 |       keyword: z.string().describe('The keyword to search for'),
288 |       location: z.string().describe('Location in format "City,Region,Country" or just "Country"'),
289 |       
290 |       // Optional parameters
291 |       fields: z.array(z.string()).optional().describe('Specific fields to return in the response. If not specified, all fields will be returned'),
292 |       language: z.string().optional().describe('Language code (e.g., "en")'),
293 |     };
294 |   }
295 | 
296 |   async handle(params: any) {
297 |     try {
298 |       // Make the API call
299 |       const response = await this.client.makeRequest({
300 |         endpoint: '/v3/dataforseo_endpoint_path',
301 |         method: 'POST',
302 |         body: [{
303 |           // Your request parameters
304 |           keyword: params.keyword,
305 |           location: params.location,
306 |           language: params.language,
307 |         }],
308 |       });
309 | 
310 |       // Validate the response for errors
311 |       this.validateResponse(response);
312 | 
313 |       //if the main data array is specified in tasks[0].result[:] field
314 |       const result = this.handleDirectResult(response);
315 |       //if main data array specified in tasks[0].result[0].items field
316 |       const result = this.handleItemsResult(response);
317 |       // Format and return the response
318 |       return this.formatResponse(result);
319 |     } catch (error) {
320 |       // Handle and format any errors
321 |       return this.formatErrorResponse(error);
322 |     }
323 |   }
324 | }
325 | ```
326 | 
327 | ### Creating a New Module
328 | 
329 | 1. Create a new directory under `src/core/modules/` for your module:
330 | ```bash
331 | mkdir -p src/core/modules/your-module-name
332 | ```
333 | 
334 | 2. Create module files:
335 | ```typescript
336 | // src/core/modules/your-module-name/your-module-name.module.ts
337 | import { BaseModule } from '../base.module';
338 | import { DataForSEOClient } from '../../client/dataforseo.client';
339 | import { YourTool } from './tools/your-tool.tool';
340 | 
341 | export class YourModuleNameModule extends BaseModule {
342 |   constructor(private client: DataForSEOClient) {
343 |     super();
344 |   }
345 | 
346 |   getTools() {
347 |     return {
348 |       'your-tool-name': new YourTool(this.client),
349 |     };
350 |   }
351 | }
352 | ```
353 | 
354 | 3. Register your module in `src/core/config/modules.config.ts`:
355 | ```typescript
356 | export const AVAILABLE_MODULES = [
357 |   'SERP',
358 |   'KEYWORDS_DATA',
359 |   'ONPAGE',
360 |   'DATAFORSEO_LABS',
361 |   'BACKLINKS',
362 |   'BUSINESS_DATA',
363 |   'DOMAIN_ANALYTICS',
364 |   'CONTENT_ANALYSIS',
365 |   'YOUR_MODULE_NAME'  // Add your module name here
366 | ] as const;
367 | ```
368 | 
369 | 4. Initialize your module in `src/main/index.ts`:
370 | ```typescript
371 | if (isModuleEnabled('YOUR_MODULE_NAME', enabledModules)) {
372 |   modules.push(new YourModuleNameModule(dataForSEOClient));
373 | }
374 | ```
375 | 
376 | ## Field Configuration
377 | 
378 | The MCP server supports field filtering to customize which data fields are returned in API responses. This helps reduce response size and focus on the most relevant data for your use case.
379 | 
380 | ### Configuration File Format
381 | 
382 | Create a JSON configuration file with the following structure:
383 | 
384 | ```json
385 | {
386 |   "supported_fields": {
387 |     "tool_name": ["field1", "field2", "field3"],
388 |     "another_tool": ["field1", "field2"]
389 |   }
390 | }
391 | ```
392 | 
393 | ### Using Field Configuration
394 | 
395 | Pass the configuration file using the `--configuration` parameter:
396 | 
397 | ```bash
398 | # With npm
399 | npm run cli -- http --configuration field-config.json
400 | 
401 | # With npx
402 | npx dataforseo-mcp-server http --configuration field-config.json
403 | 
404 | # Local mode
405 | npx dataforseo-mcp-server local --configuration field-config.json
406 | ```
407 | 
408 | ### Configuration Behavior
409 | 
410 | - **If a tool is configured**: Only the specified fields will be returned in the response
411 | - **If a tool is not configured**: All available fields will be returned (default behavior)
412 | - **If no configuration file is provided**: All tools return all available fields
413 | 
414 | ### Example Configuration File
415 | 
416 | The repository includes an example configuration file `field-config.example.json` with optimized field selections for common tools:
417 | 
418 | ```json
419 | {
420 |   "supported_fields": {
421 |     "backlinks_backlinks": [
422 |       "id",
423 |       "items.anchor",
424 |       "items.backlink_spam_score",
425 |       "items.dofollow",
426 |       "items.domain_from",
427 |       "items.domain_from_country",
428 |       "items.domain_from_ip",
429 |       "items.domain_from_platform_type",
430 |       "items.domain_from_rank",
431 |       "items.domain_to",
432 |       "items.first_seen",
433 |       "items.is_broken",
434 |       "items.is_new",
435 |       "items.item_type",
436 |       "items.last_seen",
437 |       "items.links_count",
438 |       "items.original",
439 |       "items.page_from_encoding",
440 |       "items.page_from_external_links",
441 |       "items.page_from_internal_links",
442 |       "items.page_from_language",
443 |       "items.page_from_rank",
444 |       "items.page_from_size",
445 |       "items.page_from_status_code",
446 |       "items.page_from_title",
447 |       "items.prev_seen",
448 |       "items.rank",
449 |       "items.ranked_keywords_info.page_from_keywords_count_top_10",
450 |       "items.ranked_keywords_info.page_from_keywords_count_top_100",
451 |       "items.ranked_keywords_info.page_from_keywords_count_top_3",
452 |       "items.semantic_location",
453 |       "items.text_post",
454 |       "items.text_pre",
455 |       "items.tld_from",
456 |       "items.type",
457 |       "items.url_from",
458 |       "items.url_from_https",
459 |       "items.url_to",
460 |       "items.url_to_https",
461 |       "items.url_to_spam_score",
462 |       "items.url_to_status_code",
463 |       "status_code",
464 |       "status_message"
465 |     ],
466 |     ...
467 |   }
468 | }
469 | ```
470 | 
471 | ### Nested Field Support
472 | 
473 | The configuration supports nested field paths using dot notation:
474 | 
475 | - `"rating.value"` - Access the `value` field within the `rating` object
476 | - `"items.demography.age.keyword"` - Access deeply nested fields
477 | - `"meta.description"` - Access nested object properties
478 | 
479 | ### Field Discovery
480 | 
481 | To discover available fields for any tool:
482 | 
483 | 1. Run the tool without field configuration to see the full response
484 | 2. Identify the fields you need from the API response
485 | 3. Add those field paths to your configuration file
486 | 
487 | ### Creating Your Own Configuration
488 | 
489 | 1. Copy the example file:
490 | ```bash
491 | cp field-config.example.json my-config.json
492 | ```
493 | 
494 | 2. Modify the field selections based on your needs
495 | 
496 | 3. Use your custom configuration:
497 | ```bash
498 | npx dataforseo-mcp-server http --configuration my-config.json
499 | ```
500 | 
501 | ## What endpoints/APIs do you want us to support next?
502 | 
503 | We're always looking to expand the capabilities of this MCP server. If you have specific DataForSEO endpoints or APIs you'd like to see supported, please:
504 | 
505 | 1. Check the [DataForSEO API Documentation](https://docs.dataforseo.com/v3/) to see what's available
506 | 2. Open an issue in our GitHub repository with:
507 |    - The API/endpoint you'd like to see supported;
508 |    - A brief description of your use case;
509 |    - Describe any specific features you'd like to see implemented.
510 | 
511 | Your feedback helps us prioritize which APIs to support next!
512 | 
513 | ## Resources
514 | 
515 | - [Model Context Protocol Documentation](https://modelcontextprotocol.io/quickstart)
516 | - [DataForSEO API Documentation](https://docs.dataforseo.com/)
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM node:22-alpine
 2 | WORKDIR /app
 3 | COPY package*.json ./
 4 | RUN npm ci --ignore-scripts
 5 | COPY tsconfig.json ./
 6 | COPY src/ ./src/
 7 | RUN npm run build
 8 | EXPOSE 3000
 9 | ENV NODE_ENV=production
10 | ENTRYPOINT ["node", "build/main/main/cli.js"]
11 | CMD ["http"]
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build/main/",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "types": ["node"],
13 |     "sourceMap": true
14 |   },
15 |   "include": ["src/**/*.ts"],
16 |   "exclude": ["node_modules", "src/worker/**/*"]
17 | }
18 | 
```

--------------------------------------------------------------------------------
/tsconfig.worker.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "ES2022",
 5 |     "moduleResolution": "bundler",
 6 |     "outDir": "./build/worker",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "types": ["@cloudflare/workers-types", "./src/worker/worker-configuration.d.ts"],
13 |     "lib": ["ES2022", "WebWorker"]
14 |   },
15 |   "include": [
16 |     "src/**/*.ts"
17 |   ]
18 | }
19 | 
```

--------------------------------------------------------------------------------
/src/core/modules/prompt-definition.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export interface PromptDefinition {
 4 |   name: string;
 5 |   title: string;
 6 |   description?: string;
 7 |   params?: z.ZodRawShape;
 8 |   handler: (args: any) => Promise<PromptResult>;
 9 | }
10 | 
11 | export interface PromptResult {
12 |   [x: string]: unknown;
13 |   description?: string;
14 |   messages: PromptResultMessage[];
15 |   _meta?: { [x: string]: unknown; };
16 | }
17 | 
18 | export interface PromptResultMessage {
19 |   role: "user" | "assistant";
20 |   content: {
21 |     type: "text";
22 |     text: string;
23 |   };
24 | 
25 |   [x: string]: any;
26 | }
```

--------------------------------------------------------------------------------
/src/core/utils/map-array-to-numbered-keys.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Maps an array of strings to an object with numbered keys
 3 |  * @param arr Array of strings to map
 4 |  * @returns Object with numbered keys (1-based index)
 5 |  * @example
 6 |  * const arr = ['val_1', 'val_2', 'val_3']
 7 |  * const result = mapArrayToNumberedKeys(arr)
 8 |  * // result = { '1': 'val_1', '2': 'val_2', '3': 'val_3' }
 9 |  */
10 | export function mapArrayToNumberedKeys<T extends string>(arr: T[]): Record<string, T> {
11 |     return arr.reduce((acc, val, index) => {
12 |         acc[String(index + 1)] = val;
13 |         return acc;
14 |     }, {} as Record<string, T>);
15 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/business-data-api/business-data-api.module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseModule, ToolDefinition } from '../base.module.js';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | import { BusinessDataBusinessListingsSearchTool } from './tools/listings/business-listings-search.tool.js';
 4 | 
 5 | export class BusinessDataApiModule extends BaseModule {
 6 |   getTools(): Record<string, ToolDefinition> {
 7 |     const tools = [
 8 |       new BusinessDataBusinessListingsSearchTool(this.dataForSEOClient),
 9 |       // Add more tools here
10 |     ];
11 | 
12 |     return tools.reduce((acc, tool) => ({
13 |       ...acc,
14 |       [tool.getName()]: {
15 |         description: tool.getDescription(),
16 |         params: tool.getParams(),
17 |         handler: (params: any) => tool.handle(params),
18 |       },
19 |     }), {});
20 |   }
21 | 
22 |     getPrompts(): Record<string, PromptDefinition> {
23 |       return {}
24 |     }
25 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/keywords-data/tools/google-trends/google-trends-categories.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 4 | 
 5 | export class GoogleTrendsCategoriesTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'keywords_data_google_trends_categories';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return 'This endpoint will provide you list of Google Trends Categories';
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       
21 |     };
22 |   }
23 | 
24 |   async handle(params: any): Promise<any> {
25 |     try {
26 |       const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/google_trends/categories/live', 'GET');
27 |       return this.validateAndFormatResponse(response);
28 |     } catch (error) {
29 |       return this.formatErrorResponse(error);
30 |     }
31 |   }
32 | } 
```

--------------------------------------------------------------------------------
/src/core/config/global.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { debug } from 'console';
 2 | import { z } from 'zod';
 3 | 
 4 | export const GlobalToolConfigSchema = z.object({
 5 |   simpleFilter: z.boolean().default(false),
 6 |   fullResponse: z.boolean().default(false),
 7 |   debug: z.boolean().default(false)
 8 | });
 9 | 
10 | export type GlobalToolConfig = z.infer<typeof GlobalToolConfigSchema>;
11 | 
12 | // Parse config from environment variables
13 | export function parseGlobalToolConfig(): GlobalToolConfig {
14 |   const fullResponseEnv = process.env.DATAFORSEO_FULL_RESPONSE as string;
15 |   const debugEnv = process.env.DEBUG as string;
16 |   const simpleFilterEnv = process.env.DATAFORSEO_SIMPLE_FILTER as string;
17 |   const config = {
18 |     fullResponse: fullResponseEnv === 'true',
19 |     debug: debugEnv === 'true',
20 |     simpleFilter: simpleFilterEnv === 'true'
21 |   };
22 |   
23 |   return GlobalToolConfigSchema.parse(config);
24 | }
25 | 
26 | // Export default config
27 | export const defaultGlobalToolConfig = parseGlobalToolConfig(); 
```

--------------------------------------------------------------------------------
/scripts/generate-worker-version.cjs:
--------------------------------------------------------------------------------

```
 1 | const fs = require('fs');
 2 | const path = require('path');
 3 | 
 4 | // Read package.json
 5 | const packageJsonPath = path.join(__dirname, '../package.json');
 6 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
 7 | 
 8 | // Generate worker version file content
 9 | const content = `// Auto-generated from package.json - do not edit manually
10 | // Generated on: ${new Date().toISOString()}
11 | 
12 | export const version = "${packageJson.version}";
13 | export const name = "${packageJson.name}";
14 | 
15 | export default {
16 |   version,
17 |   name
18 | };
19 | `;
20 | 
21 | // Ensure the worker directory exists
22 | const workerDir = path.join(__dirname, '../src/worker');
23 | if (!fs.existsSync(workerDir)) {
24 |   fs.mkdirSync(workerDir, { recursive: true });
25 | }
26 | 
27 | // Write the version file
28 | const outputPath = path.join(workerDir, 'version.worker.ts');
29 | fs.writeFileSync(outputPath, content);
30 | 
31 | console.log(`✅ Generated worker version file with version ${packageJson.version}`);
32 | console.log(`📁 Output: ${outputPath}`);
33 | 
```

--------------------------------------------------------------------------------
/src/core/modules/ai-optimization/ai-optimization-api-module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseModule, ToolDefinition } from '../base.module.js';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | import { AiOptimizationKeywordDataLocationsAndLanguagesListTool } from './tools/keyword-data/ai-optimization-keyword-data-locations-and-languages.js';
 4 | import { AiOptimizationKeywordDataSearchVolumeTool } from './tools/keyword-data/ai-optimization-keyword-data-search-volume.js'
 5 | 
 6 | export class AiOptimizationApiModule extends BaseModule {
 7 |   getTools(): Record<string, ToolDefinition> {
 8 |     const tools = [
 9 |       new AiOptimizationKeywordDataLocationsAndLanguagesListTool(this.dataForSEOClient),
10 |       new AiOptimizationKeywordDataSearchVolumeTool(this.dataForSEOClient),
11 |     ];
12 | 
13 |     return tools.reduce((acc, tool) => ({
14 |       ...acc,
15 |       [tool.getName()]: {
16 |         description: tool.getDescription(),
17 |         params: tool.getParams(),
18 |         handler: (params: any) => tool.handle(params),
19 |       },
20 |     }), {});
21 |   }
22 | 
23 |     getPrompts(): Record<string, PromptDefinition> {
24 |       return {};
25 |     }
26 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/content-analysis/content-analysis-api.module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseModule, ToolDefinition } from '../base.module.js';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | import { ContentAnalysisPhraseTrendsTool } from './tools/content-analysis-phrase-trends.js';
 4 | import { ContentAnalysisSearchTool } from './tools/content-analysis-search.tool.js';
 5 | import { ContentAnalysisSummaryTool } from './tools/content-analysis-summary.js';
 6 | 
 7 | export class ContentAnalysisApiModule extends BaseModule {
 8 |   getTools(): Record<string, ToolDefinition> {
 9 |     const tools = [
10 |       new ContentAnalysisSearchTool(this.dataForSEOClient),
11 |       new ContentAnalysisSummaryTool(this.dataForSEOClient),
12 |       new ContentAnalysisPhraseTrendsTool(this.dataForSEOClient),
13 |       // Add more tools here
14 |     ];
15 | 
16 |     return tools.reduce((acc, tool) => ({
17 |       ...acc,
18 |       [tool.getName()]: {
19 |         description: tool.getDescription(),
20 |         params: tool.getParams(),
21 |         handler: (params: any) => tool.handle(params),
22 |       },
23 |     }), {});
24 |   }
25 | 
26 |   getPrompts(): Record<string, PromptDefinition> {
27 |     return {}
28 |   }
29 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/ai-optimization/tools/keyword-data/ai-optimization-keyword-data-locations-and-languages.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';
 2 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 3 | import { ZodRawShape } from 'zod';
 4 | 
 5 | export class AiOptimizationKeywordDataLocationsAndLanguagesListTool extends BaseTool {
 6 | 
 7 |   constructor(dataForSEOClient: DataForSEOClient) {
 8 |     super(dataForSEOClient);
 9 |   }
10 | 
11 |   protected supportOnlyFullResponse(): boolean {
12 |     return true;
13 |   }
14 |   
15 |   getName(): string {
16 |       return "ai_optimization_keyword_data_locations_and_languages";
17 |   }
18 | 
19 |   getDescription(): string {
20 |       return "Utility tool for ai_keyword_data_search_volume to get list of availible locations and languages";
21 |   }
22 | 
23 |   getParams(): ZodRawShape {
24 |      return {};
25 |   }
26 | 
27 |   async handle(params: any): Promise<any> {
28 |      try {
29 | 
30 |       const response = await this.dataForSEOClient.makeRequest(`/v3/ai_optimization/ai_keyword_data/locations_and_languages`, 'GET', null);
31 |       return this.validateAndFormatResponse(response);
32 |     } catch (error) {
33 |       return this.formatErrorResponse(error);
34 |     }
35 |   }
36 | 
37 | }
```

--------------------------------------------------------------------------------
/src/core/modules/domain-analytics/tools/technologies/domain-technologies.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../../base.tool.js';
 4 | 
 5 | export class DomainTechnologiesTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'domain_analytics_technologies_domain_technologies';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `Using this endpoint you will get a list of technologies used in a particular domain`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`target domain
21 | required field
22 | domain name of the website to analyze
23 | Note: results will be returned for the specified domain only`)      
24 |       }
25 |   }
26 | 
27 |   async handle(params: any): Promise<any> {
28 |     try {
29 |       const response = await this.client.makeRequest('/v3/domain_analytics/technologies/domain_technologies/live', 'POST', [{
30 |         target: params.target
31 |       }]);
32 |       return this.validateAndFormatResponse(response);
33 |     } catch (error) {
34 |       return this.formatErrorResponse(error);
35 |     }
36 |   }
37 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/domain-analytics/domain-analytics-api.module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseModule, ToolDefinition } from '../base.module.js';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | import { DomainTechnologiesTool } from './tools/technologies/domain-technologies.tool.js';
 4 | import { DomainTechnologiesFiltersTool } from './tools/technologies/domain-technologies-filters.tool.js';
 5 | import { WhoisFiltersTool } from './tools/whois/whois-filters.tool.js';
 6 | import { WhoisOverviewTool } from './tools/whois/whois-overview.tool.js';
 7 | 
 8 | export class DomainAnalyticsApiModule extends BaseModule {
 9 |   getTools(): Record<string, ToolDefinition> {
10 |     const tools = [
11 |       new WhoisOverviewTool(this.dataForSEOClient),
12 |       new WhoisFiltersTool(this.dataForSEOClient),
13 |       new DomainTechnologiesTool(this.dataForSEOClient),
14 |       new DomainTechnologiesFiltersTool(this.dataForSEOClient),
15 |       // Add more tools here
16 |     ];
17 | 
18 |     return tools.reduce((acc, tool) => ({
19 |       ...acc,
20 |       [tool.getName()]: {
21 |         description: tool.getDescription(),
22 |         params: tool.getParams(),
23 |         handler: (params: any) => tool.handle(params),
24 |       },
25 |     }), {});
26 |   }
27 | 
28 |   getPrompts(): Record<string, PromptDefinition> {
29 |     return {};
30 |   }
31 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/base.module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { DataForSEOClient } from '../client/dataforseo.client.js';
 2 | import { z } from 'zod';
 3 | import { PromptDefinition } from './prompt-definition.js';
 4 | 
 5 | export interface ToolDefinition {
 6 |   description: string;
 7 |   params: z.ZodRawShape;
 8 |   handler: (params: any) => Promise<any>;
 9 | }
10 | 
11 | export abstract class BaseModule {
12 |   protected dataForSEOClient: DataForSEOClient;
13 | 
14 |   constructor(dataForSEOClient: DataForSEOClient) {
15 |     this.dataForSEOClient = dataForSEOClient;
16 |   }
17 | 
18 |   protected formatError(error: unknown): string {
19 |     return error instanceof Error ? error.message : 'Unknown error';
20 |   }
21 | 
22 |   protected formatResponse(data: any): { content: Array<{ type: string; text: string }> } {
23 |     return {
24 |       content: [
25 |         {
26 |           type: "text",
27 |           text: JSON.stringify(data, null, 2),
28 |         },
29 |       ],
30 |     };
31 |   }
32 | 
33 |   protected formatErrorResponse(error: unknown): { content: Array<{ type: string; text: string }> } {
34 |     return {
35 |       content: [
36 |         {
37 |           type: "text",
38 |           text: `Error: ${this.formatError(error)}`,
39 |         },
40 |       ],
41 |     };
42 |   }
43 | 
44 |   abstract getTools(): Record<string, ToolDefinition>;
45 | 
46 |   abstract getPrompts(): Record<string, PromptDefinition>;
47 | } 
```

--------------------------------------------------------------------------------
/src/core/config/modules.config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | // Define available module names
 4 | export const AVAILABLE_MODULES = ['AI_OPTIMIZATION', 'SERP', 'KEYWORDS_DATA', 'ONPAGE', 'DATAFORSEO_LABS', 'BACKLINKS', 'BUSINESS_DATA', 'DOMAIN_ANALYTICS', 'CONTENT_ANALYSIS'] as const;
 5 | export type ModuleName = typeof AVAILABLE_MODULES[number];
 6 | 
 7 | // Schema for validating the ENABLED_MODULES environment variable
 8 | export const EnabledModulesSchema = z.any()
 9 |   .transform((val:string) => {
10 |     if (!val) return AVAILABLE_MODULES; // If not set, enable all modules
11 |     return val.toString().split(',').map(name => name.trim().toUpperCase() as ModuleName);
12 |   })
13 |   .refine((modules) => {
14 |     return modules.every(module => AVAILABLE_MODULES.includes(module));
15 |   }, {
16 |     message: `Invalid module name. Available modules are: ${AVAILABLE_MODULES.join(', ')}`
17 |   });
18 | 
19 | export type EnabledModules = z.infer<typeof EnabledModulesSchema>;
20 | 
21 | // Helper function to check if a module is enabled
22 | export function isModuleEnabled(moduleName: ModuleName, enabledModules: EnabledModules): boolean {
23 |   return enabledModules.includes(moduleName);
24 | }
25 | 
26 | // Default configuration (all modules enabled)
27 | export const defaultEnabledModules: EnabledModules = AVAILABLE_MODULES; 
```

--------------------------------------------------------------------------------
/src/core/modules/onpage/onpage-api.module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseModule, ToolDefinition } from '../base.module.js';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | import { onpagePrompts } from './onpage.prompt.js';
 4 | import { ContentParsingTool } from './tools/content-parsing.tool.js';
 5 | import { InstantPagesTool } from './tools/instant-pages.tool.js';
 6 | import { LighthouseTool } from './tools/lighthouse.tool.js';
 7 | 
 8 | export class OnPageApiModule extends BaseModule {
 9 |   getTools(): Record<string, ToolDefinition> {
10 |     const tools = [
11 |       new ContentParsingTool(this.dataForSEOClient),
12 |       new InstantPagesTool(this.dataForSEOClient),
13 |       new LighthouseTool(this.dataForSEOClient),
14 |       // Add more tools here
15 |     ];
16 | 
17 |     return tools.reduce((acc, tool) => ({
18 |       ...acc,
19 |       [tool.getName()]: {
20 |         description: tool.getDescription(),
21 |         params: tool.getParams(),
22 |         handler: (params: any) => tool.handle(params),
23 |       },
24 |     }), {});
25 |   }
26 | 
27 |     getPrompts(): Record<string, PromptDefinition> {
28 |       return onpagePrompts.reduce((acc, prompt) => ({
29 |         ...acc,
30 |         [prompt.name]: {
31 |           description: prompt.description,
32 |           params: prompt.params,
33 |           handler: (params: any) => {
34 | 
35 |             return prompt.handler(params);
36 |           },
37 |         },
38 |       }), {});
39 |     }
40 | } 
41 | 
```

--------------------------------------------------------------------------------
/src/main/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 4 | import { DataForSEOClient, DataForSEOConfig } from '../core/client/dataforseo.client.js';
 5 | import { EnabledModulesSchema, isModuleEnabled, defaultEnabledModules } from '../core/config/modules.config.js';
 6 | import { BaseModule, ToolDefinition } from '../core/modules/base.module.js';
 7 | import { z } from 'zod';
 8 | import { ModuleLoaderService } from "../core/utils/module-loader.js";
 9 | import { initializeFieldConfiguration } from '../core/config/field-configuration.js';
10 | import { name, version } from '../core/utils/version.js';
11 | import { initMcpServer } from "./init-mcp-server.js";
12 | 
13 | // Initialize field configuration if provided
14 | initializeFieldConfiguration();
15 | console.error('Starting DataForSEO MCP Server...');
16 | console.error(`Server name: ${name}, version: ${version}`);
17 | 
18 | const server = initMcpServer(process.env.DATAFORSEO_USERNAME, process.env.DATAFORSEO_PASSWORD);
19 | 
20 | // Start the server
21 | async function main() {
22 |   const transport = new StdioServerTransport(); 
23 |   console.error('Starting server');
24 |   await server.connect(transport);
25 |   console.error("DataForSEO MCP Server running on stdio");
26 | }
27 | 
28 | main().catch((error) => {
29 |   console.error("Fatal error in main():", error);
30 |   process.exit(1);
31 | });
32 | 
```

--------------------------------------------------------------------------------
/src/core/modules/keywords-data/tools/google-ads/google-ads-search-volume.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 4 | 
 5 | export class GoogleAdsSearchVolumeTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'keywords_data_google_ads_search_volume';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return 'Get search volume data for keywords from Google Ads';
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       location_name: z.string().nullable().default(null).describe(`full name of the location
21 | optional field
22 | in format "Country"
23 | example:
24 | United Kingdom`),
25 |               language_code: z.string().nullable().default(null).describe(`Language two-letter ISO code (e.g., 'en').
26 | optional field`),
27 |       keywords: z.array(z.string()).describe("Array of keywords to get search volume for"),
28 |     };
29 |   }
30 | 
31 |   async handle(params: any): Promise<any> {
32 |     try {
33 |       const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/google_ads/search_volume/live', 'POST', [{
34 |         location_name: params.location_name,
35 |         language_code: params.language_code,
36 |         keywords: params.keywords,
37 |       }]);
38 |       return this.validateAndFormatResponse(response);
39 |     } catch (error) {
40 |       return this.formatErrorResponse(error);
41 |     }
42 |   }
43 | } 
```

--------------------------------------------------------------------------------
/src/core/client/dataforseo.client.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defaultGlobalToolConfig } from '../config/global.tool.js';
 2 | 
 3 | export class DataForSEOClient {
 4 |   private config: DataForSEOConfig;
 5 |   private authHeader: string;
 6 | 
 7 |   constructor(config: DataForSEOConfig) {
 8 |     this.config = config;
 9 |     if(defaultGlobalToolConfig.debug) {
10 |       console.error('DataForSEOClient initialized with config:', config);
11 |     }
12 |     const token = btoa(`${config.username}:${config.password}`);
13 |     this.authHeader = `Basic ${token}`;
14 |   }
15 | 
16 |   async makeRequest<T>(endpoint: string, method: string = 'POST', body?: any, forceFull: boolean = false): Promise<T> {
17 |     let url = `${this.config.baseUrl || "https://api.dataforseo.com"}${endpoint}`;    
18 |     if(!defaultGlobalToolConfig.fullResponse && !forceFull){
19 |       url += '.ai';
20 |     }
21 |     // Import version dynamically to avoid circular dependencies
22 |     const { version } = await import('../utils/version.js');
23 |     
24 |     const headers = {
25 |       'Authorization': this.authHeader,
26 |       'Content-Type': 'application/json',
27 |       'User-Agent': `DataForSEO-MCP-TypeScript-SDK/${version}`
28 |     };
29 | 
30 |     console.error(`Making request to ${url} with method ${method} and body`, body);
31 |     const response = await fetch(url, {
32 |       method,
33 |       headers,
34 |       body: body ? JSON.stringify(body) : undefined,
35 |     });
36 | 
37 |     if (!response.ok) {
38 |       throw new Error(`HTTP error! status: ${response.status}`);
39 |     }
40 | 
41 |     return response.json();
42 |   }
43 | } 
44 | 
45 | export interface DataForSEOConfig {
46 |   username: string;
47 |   password: string;
48 |   baseUrl?: string;
49 | }
```

--------------------------------------------------------------------------------
/src/core/modules/ai-optimization/tools/keyword-data/ai-optimization-keyword-data-search-volume.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 4 | import { ZodRawShape } from 'zod';
 5 | 
 6 | export class AiOptimizationKeywordDataSearchVolumeTool extends BaseTool {
 7 | 
 8 |   constructor(dataForSEOClient: DataForSEOClient) {
 9 |     super(dataForSEOClient);
10 |   }
11 |   
12 |   getName(): string {
13 |       return "ai_optimization_keyword_data_search_volume";
14 |   }
15 | 
16 |   getDescription(): string {
17 |       return "This endpoint provides search volume data for your target keywords, reflecting their estimated usage in AI LLMs";
18 |   }
19 | 
20 |   getParams(): z.ZodRawShape {
21 |     return {
22 |       keywords: z.array(z.string()).describe("Keywords. The maximum number of keywords you can specify: 1000"),
23 |       location_name: z.string().default('United States').describe(`full name of the location, example: 'United Kingdom', 'United States'`),
24 |       language_code: z.string().describe("Search engine language code (e.g., 'en')"),
25 |     };
26 |   }
27 | 
28 |   async handle(params: any): Promise<any> {
29 |     try {
30 |       console.error(JSON.stringify(params, null, 2));
31 |       const response = await this.dataForSEOClient.makeRequest(`/v3/ai_optimization/ai_keyword_data/keywords_search_volume/live`, 'POST', [{
32 |         keywords: params.keywords,
33 |         location_name: params.location_name,
34 |         language_code: params.language_code
35 |       }]);
36 |       return this.validateAndFormatResponse(response);
37 |     } catch (error) {
38 |       return this.formatErrorResponse(error);
39 |     }
40 |   }
41 | 
42 | }
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-spam-score.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkSpamScoreTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_spam_score';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with spam scores of the domains, subdomains, and pages you specified in the targets array. Spam Score is DataForSEO’s proprietary metric that indicates how “spammy” your target is on a scale from 0 to 100`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
21 | required field
22 | you can set up to 1000 domains, subdomains or webpages
23 | the domain or subdomain should be specified without https:// and www.
24 | the page should be specified with absolute URL (including http:// or https://)
25 | example:
26 | "targets": [
27 | "forbes.com",
28 | "cnn.com",
29 | "bbc.com",
30 | "yelp.com",
31 | "https://www.apple.com/iphone/",
32 | "https://ahrefs.com/blog/",
33 | "ibm.com",
34 | "https://variety.com/",
35 | "https://stackoverflow.com/",
36 | "www.trustpilot.com"
37 | ]`)
38 |     };
39 |   }
40 | 
41 |   async handle(params: any): Promise<any> {
42 |     try {
43 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_spam_score/live', 'POST', [{
44 |         targets: params.targets
45 |       }]);
46 |       return this.validateAndFormatResponse(response);
47 |     } catch (error) {
48 |       return this.formatErrorResponse(error);
49 |     }
50 |   }
51 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-spam-score.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool, DataForSEOResponse } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkSpamScoreTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_spam_score_tool';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with spam scores of the domains, subdomains, and pages you specified in the targets array. Spam Score is DataForSEO’s proprietary metric that indicates how “spammy” your target is on a scale from 0 to 100`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
21 | required field
22 | you can set up to 1000 domains, subdomains or webpages
23 | the domain or subdomain should be specified without https:// and www.
24 | the page should be specified with absolute URL (including http:// or https://)
25 | example:
26 | "targets": [
27 | "forbes.com",
28 | "cnn.com",
29 | "bbc.com",
30 | "yelp.com",
31 | "https://www.apple.com/iphone/",
32 | "https://ahrefs.com/blog/",
33 | "ibm.com",
34 | "https://variety.com/",
35 | "https://stackoverflow.com/",
36 | "www.trustpilot.com"
37 | ]`)
38 |     };
39 |   }
40 | 
41 |   async handle(params: any): Promise<any> {
42 |     try {
43 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_spam_score/live', 'POST', [{
44 |         targets: params.targets
45 |       }]);
46 |       return this.validateAndFormatResponse(response);
47 |     } catch (error) {
48 |       return this.formatErrorResponse(error);
49 |     }
50 |   }
51 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/keywords-data/keywords-data-api.module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseModule, ToolDefinition } from '../base.module.js';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | import { DataForSeoTrendsDemographyTool } from './tools/dataforseo-trends/dataforseo-trends-demography.tool.js';
 4 | import { DataForSeoTrendsExploreTool } from './tools/dataforseo-trends/dataforseo-trends-explore.tool.js';
 5 | import { DataForSeoTrendsSubregionInterestsTool } from './tools/dataforseo-trends/dataforseo-trends-subregion-interests.tool.js';
 6 | import { GoogleAdsSearchVolumeTool } from './tools/google-ads/google-ads-search-volume.tool.js';
 7 | import { GoogleTrendsCategoriesTool } from './tools/google-trends/google-trends-categories.tool.js';
 8 | import { GoogleTrendsExploreTool } from './tools/google-trends/google-trends-explore.tool.js';
 9 | 
10 | export class KeywordsDataApiModule extends BaseModule {
11 |   getTools(): Record<string, ToolDefinition> {
12 |     const tools = [
13 |       new GoogleAdsSearchVolumeTool(this.dataForSEOClient),
14 | 
15 |       new DataForSeoTrendsDemographyTool(this.dataForSEOClient),
16 |       new DataForSeoTrendsSubregionInterestsTool(this.dataForSEOClient),
17 |       new DataForSeoTrendsExploreTool(this.dataForSEOClient),
18 | 
19 |       new GoogleTrendsCategoriesTool(this.dataForSEOClient),
20 |       new GoogleTrendsExploreTool(this.dataForSEOClient),
21 |       // Add more tools here
22 |     ];
23 | 
24 |     return tools.reduce((acc, tool) => ({
25 |       ...acc,
26 |       [tool.getName()]: {
27 |         description: tool.getDescription(),
28 |         params: tool.getParams(),
29 |         handler: (params: any) => tool.handle(params),
30 |       },
31 |     }), {});
32 |   }
33 | 
34 |   getPrompts(): Record<string, PromptDefinition> {
35 |     return {};
36 |   }
37 | } 
```

--------------------------------------------------------------------------------
/src/main/cli.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { spawn } from 'child_process';
 4 | import { fileURLToPath } from 'url';
 5 | import { dirname, join } from 'path';
 6 | 
 7 | const __filename = fileURLToPath(import.meta.url);
 8 | const __dirname = dirname(__filename);
 9 | 
10 | const args = process.argv.slice(2);
11 | // Parse command line arguments
12 | const mode = args[0] || 'local';
13 | const configIndex = args.indexOf('--configuration');
14 | const configPath = configIndex !== -1 && configIndex + 1 < args.length ? args[configIndex + 1] : null;
15 | const debugLog = args.includes('--debug') || args.includes('-d');
16 | // Set environment variable for configuration path if provided
17 | if (configPath) {
18 |     process.env.FIELD_CONFIG_PATH = configPath;
19 |     console.error(`Using field configuration: ${configPath}`);
20 | }
21 | if( debugLog) {
22 |     console.error('Debug mode enabled');
23 |     process.env.DEBUG = 'true';
24 | }
25 | // Prepare arguments to pass to the spawned process (excluding --configuration args)
26 | const argsWithoutMode = args.slice(1);
27 | const childArgs = argsWithoutMode.filter((_, index) => {
28 |     return index !== configIndex - 1 && index !== configIndex;});
29 |     
30 | if (mode === 'http') {
31 |     const httpServer = join(__dirname, 'index-http.js');
32 |     spawn('node', [httpServer, ...childArgs], { 
33 |         stdio: 'inherit',
34 |         env: { ...process.env }
35 |     });
36 | } else if (mode === 'sse') {
37 |     const sseServer = join(__dirname, 'index-sse-http.js');
38 |     spawn('node', [sseServer, ...childArgs], { 
39 |         stdio: 'inherit',
40 |         env: { ...process.env }
41 |     });
42 | } else {
43 |     const localServer = join(__dirname, 'index.js');
44 |     spawn('node', [localServer, ...childArgs], { 
45 |         stdio: 'inherit',
46 |         env: { ...process.env }
47 |     });
48 | }
```

--------------------------------------------------------------------------------
/src/core/modules/onpage/tools/lighthouse.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | import { DataForSEOResponse } from '../../base.tool.js';
 5 | import { defaultGlobalToolConfig } from '../../../config/global.tool.js';
 6 | 
 7 | export class LighthouseTool extends BaseTool {
 8 |   constructor(private client: DataForSEOClient) {
 9 |     super(client);
10 |   }
11 | 
12 |   getName(): string {
13 |     return 'on_page_lighthouse';
14 |   }
15 | 
16 |   getDescription(): string {
17 |     return 'The OnPage Lighthouse API is based on Google’s open-source Lighthouse project for measuring the quality of web pages and web apps.';
18 |   }
19 | 
20 |   getParams(): z.ZodRawShape {
21 |     return {
22 |       url: z.string().describe("URL of the page to parse"),
23 |       enable_javascript: z.boolean().optional().describe("Enable JavaScript rendering"),
24 |       custom_js: z.string().optional().describe("Custom JavaScript code to execute"),
25 |       custom_user_agent: z.string().optional().describe("Custom User-Agent header"),
26 |       accept_language: z.string().optional().describe("Accept-Language header value"),
27 |     };
28 |   }
29 | 
30 |   async handle(params: any): Promise<any> {
31 |     try {
32 |         const response = await this.dataForSEOClient.makeRequest('/v3/on_page/lighthouse/live/json', 'POST', [{
33 |           url: params.url,
34 |           enable_javascript: params.enable_javascript,
35 |           custom_js: params.custom_js,
36 |           custom_user_agent: params.custom_user_agent,
37 |           accept_language: params.accept_language,
38 |         }]);
39 |         return this.validateAndFormatResponse(response);
40 |       } catch (error) {
41 |         return this.formatErrorResponse(error);
42 |       }
43 |   }
44 | } 
45 | 
```

--------------------------------------------------------------------------------
/src/core/utils/version.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Environment detection and version loading for both Node.js and Workers
 2 | let packageVersion = '1.0.0'; // Default version
 3 | let packageName = 'dataforseo-mcp-server'; // Default name
 4 | 
 5 | // Type declarations for worker environment globals
 6 | declare global {
 7 |   var __PACKAGE_VERSION__: string | undefined;
 8 |   var __PACKAGE_NAME__: string | undefined;
 9 | }
10 | 
11 | // Check if we're in a Node.js environment (has fs module)
12 | const isNodeEnvironment = typeof globalThis !== 'undefined' && 
13 |   typeof globalThis.process !== 'undefined' && 
14 |   globalThis.process.versions?.node;
15 | 
16 | if (isNodeEnvironment) {
17 |   // Node.js environment - read from package.json
18 |   try {
19 |     const fs = await import('fs');
20 |     const path = await import('path');
21 |     const { fileURLToPath } = await import('url');
22 | 
23 |     // Get the directory of the current module
24 |     const __filename = fileURLToPath(import.meta.url);
25 |     const __dirname = path.dirname(__filename);
26 | 
27 |     const packageJsonPath = path.resolve(__dirname, '../../../../package.json');
28 |       const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
29 |     packageVersion = packageJson.version || packageVersion;
30 |     packageName = packageJson.name || packageName;
31 |   } catch (error: unknown) {
32 |     const errorMessage = error instanceof Error ? error.message : String(error);
33 |     console.warn('Could not read package.json, using default version:', errorMessage);
34 |   }
35 | } else {
36 |   // Worker environment - use compile-time constants
37 |   // These will be replaced by the build process or use defaults
38 |   packageVersion = globalThis.__PACKAGE_VERSION__ || packageVersion;
39 |   packageName = globalThis.__PACKAGE_NAME__ || packageName;
40 | }
41 | 
42 | export const version = packageVersion;
43 | export const name = packageName;
44 | 
45 | export default {
46 |   version,
47 |   name
48 | };
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/competitor-research/google-domain-rank-overview.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../../../base.tool.js';
 4 | 
 5 | export class GoogleDomainRankOverviewTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'dataforseo_labs_google_domain_rank_overview';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with ranking and traffic data from organic and paid search for the specified domain. You will be able to review the domain ranking distribution in SERPs as well as estimated monthly traffic volume for both organic and paid results.`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`target domain`),
21 |       location_name: z.string().default("United States").describe(`full name of the location
22 | required field
23 | only in format "Country" (not "City" or "Region")
24 | example:
25 | 'United Kingdom', 'United States', 'Canada'`),
26 |       language_code: z.string().default("en").describe(
27 |         `language code
28 |         required field
29 |         example:
30 |         en`),
31 |       ignore_synonyms: z.boolean().default(true).describe(
32 |           `ignore highly similar keywords, if set to true, results will be more accurate`)
33 |     };
34 |   }
35 | 
36 |   async handle(params: any): Promise<any> {
37 |     try {
38 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/domain_rank_overview/live', 'POST', [{
39 |         target: params.target,
40 |         location_name: params.location_name,
41 |         language_code: params.language_code,
42 |         ignore_synonyms: params.ignore_synonyms
43 |       }]);
44 |       return this.validateAndFormatResponse(response);
45 |     } catch (error) {
46 |       return this.formatErrorResponse(error);
47 |     }
48 |   }
49 | } 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "dataforseo-mcp-server",
 3 |   "version": "2.7.12",
 4 |   "main": "build/main/main/index.js",
 5 |   "type": "module",
 6 |   "bin": {
 7 |     "dataforseo-mcp-server": "./build/main/main/cli.js"
 8 |   },
 9 |   "scripts": {
10 |     "build": "tsc && node -e \"require('fs').chmodSync('build/main/main/cli.js', '755')\"",
11 |     "start": "node build/main/main/index.js",
12 |     "dev": "tsc --watch",
13 |     "prepare": "npm run build",
14 |     "http": "node build/main/main/index-http.js",
15 |     "sse": "node build/main/main/index-sse-http.js",
16 |     "cli": "node build/main/main/cli.js",
17 |     "worker:prebuild": "node scripts/generate-worker-version.cjs && wrangler types ./src/worker/worker-configuration.d.ts",
18 |     "worker:build": "npm run worker:prebuild && tsc --project tsconfig.worker.json",
19 |     "worker:dev": "npm run worker:build && wrangler dev",
20 |     "worker:deploy": "npm run worker:build && wrangler deploy"
21 |   },
22 |   "files": [
23 |     "build"
24 |   ],
25 |   "keywords": [
26 |     "dataforseo",
27 |     "mcp",
28 |     "modelcontextprotocol",
29 |     "api",
30 |     "server",
31 |     "seo",
32 |     "sse",
33 |     "streaming"
34 |   ],
35 |   "author": "dataforseo",
36 |   "license": "Apache-2.0",
37 |   "description": "A Model Context Protocol (MCP) server for the DataForSEO API, enabling modular and extensible integration of DataForSEO endpoints with support for both HTTP and SSE transports.",
38 |   "repository": {
39 |     "type": "git",
40 |     "url": "git+https://github.com/dataforseo/mcp-server-typescript.git"
41 |   },
42 |   "homepage": "https://github.com/dataforseo/mcp-server-typescript#readme",
43 |   "engines": {
44 |     "node": ">=20.0.0"
45 |   },
46 |   "devDependencies": {
47 |     "@types/express": "^5.0.1",
48 |     "@types/node": "^22.10.0",
49 |     "typescript": "^5.7.2"
50 |   },
51 |   "dependencies": {
52 |     "@modelcontextprotocol/sdk": "^1.4.0",
53 |     "agents": "^0.0.101",
54 |     "express": "^4.18.2",
55 |     "wrangler": "^4.24.0",
56 |     "zod": "^3.25.67"
57 |   }
58 | }
59 | 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-backlinks.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkBacklinksTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_backlinks';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the number of backlinks pointing to domains, subdomains, and pages specified in the targets array. The returned numbers correspond to all live backlinks, that is, total number of referring links with all attributes (e.g., nofollow, noreferrer, ugc, sponsored etc) that were found during the latest check.
16 | Note that if you indicate a domain as a target, you will get results for the root domain (domain with all of its subdomains), e.g. dataforseo.com and app.dataforseo.com`;
17 |   }
18 | 
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
22 | required field
23 | you can set up to 1000 domains, subdomains or webpages
24 | the domain or subdomain should be specified without https:// and www.
25 | the page should be specified with absolute URL (including http:// or https://)
26 | example:
27 | "targets": [
28 | "forbes.com",
29 | "cnn.com",
30 | "bbc.com",
31 | "yelp.com",
32 | "https://www.apple.com/iphone/",
33 | "https://ahrefs.com/blog/",
34 | "ibm.com",
35 | "https://variety.com/",
36 | "https://stackoverflow.com/",
37 | "www.trustpilot.com"
38 | ]`)
39 |     };
40 |   }
41 | 
42 |   async handle(params: any): Promise<any> {
43 |     try {
44 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_backlinks/live', 'POST', [{
45 |         targets: params.targets
46 |       }]);
47 |       return this.validateAndFormatResponse(response);
48 |     } catch (error) {
49 |       return this.formatErrorResponse(error);
50 |     }
51 |   }
52 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/keyword-research/google-search-intent.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../../../base.tool.js';
 4 | 
 5 | export class GoogleSearchIntentTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'dataforseo_labs_search_intent';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with search intent data for up to 1,000 keywords. For each keyword that you specify when setting a task, the API will return the keyword's search intent and intent probability. Besides the highest probable search intent, the results will also provide you with other likely search intent(s) and their probability.
16 | Based on keyword data and search results data, our system has been trained to detect four types of search intent: informational, navigational, commercial, transactional.`;
17 |   }
18 | 
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       keywords: z.array(z.string()).describe(`target keywords
22 | required field
23 | UTF-8 encoding
24 | maximum number of keywords you can specify in this array: 1000`),
25 |       language_code: z.string().default("en").describe(
26 |         `language code
27 |         required field
28 |         Note: this endpoint currently supports the following languages only:
29 | ar,
30 | zh-TW,
31 | cs,
32 | da,
33 | nl,
34 | en,
35 | fi,
36 | fr,
37 | de,
38 | he,
39 | hi,
40 | it,
41 | ja,
42 | ko,
43 | ms,
44 | nb,
45 | pl,
46 | pt,
47 | ro,
48 | ru,
49 | es,
50 | sv,
51 | th,
52 | uk,
53 | vi,
54 | bg,
55 | hr,
56 | sr,
57 | sl,
58 | bs`),
59 |     };
60 |   }
61 | 
62 |   async handle(params: any): Promise<any> {
63 |     try {
64 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/search_intent/live', 'POST', [{
65 |         keywords: params.keywords,
66 |         language_code: params.language_code
67 |       }]);
68 |       return this.validateAndFormatResponse(response);
69 |     } catch (error) {
70 |       return this.formatErrorResponse(error);
71 |     }
72 |   }
73 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/keyword-research/google-bulk-keyword-difficulty.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../../../base.tool.js';
 4 | 
 5 | export class GoogleBulkKeywordDifficultyTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'dataforseo_labs_bulk_keyword_difficulty';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the Keyword Difficulty metric for a maximum of 1,000 keywords in one API request. Keyword Difficulty stands for the relative difficulty of ranking in the first top-10 organic results for the related keyword. Keyword Difficulty in DataForSEO API responses indicates the chance of getting in top-10 organic results for a keyword on a logarithmic scale from 0 to 100.`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       keywords: z.array(z.string()).describe(`target keywords
21 | required field
22 | UTF-8 encoding
23 | maximum number of keywords you can specify in this array: 1000`),
24 |       location_name: z.string().default("United States").describe(`full name of the location
25 | required field
26 | only in format "Country" (not "City" or "Region")
27 | example:
28 | 'United Kingdom', 'United States', 'Canada'`),
29 |       language_code: z.string().default("en").describe(
30 |         `language code
31 |         required field
32 |         example:
33 |         en`),
34 |     };
35 |   }
36 | 
37 |   async handle(params: any): Promise<any> {
38 |     try {
39 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/bulk_keyword_difficulty/live', 'POST', [{
40 |         keywords: params.keywords,
41 |         location_name: params.location_name,
42 |         language_code: params.language_code
43 |       }]);
44 |       return this.validateAndFormatResponse(response);
45 |     } catch (error) {
46 |       return this.formatErrorResponse(error);
47 |     }
48 |   }
49 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-referring-domains.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkReferringDomainsTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_referring_domains';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the number of referring domains pointing to domains, subdomains, and pages specified in the targets array. The returned numbers are based on all live referring domains, that is, total number of domains pointing to the target with any type of backlinks (e.g., nofollow, noreferrer, ugc, sponsored etc) that were found during the latest check.
16 | Note that if you indicate a domain as a target, you will get result for the root domain (domain with all of its subdomains), e.g. dataforseo.com and app.dataforseo.com`;
17 |   }
18 | 
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
22 | required field
23 | you can set up to 1000 domains, subdomains or webpages
24 | the domain or subdomain should be specified without https:// and www.
25 | the page should be specified with absolute URL (including http:// or https://)
26 | example:
27 | "targets": [
28 | "forbes.com",
29 | "cnn.com",
30 | "bbc.com",
31 | "yelp.com",
32 | "https://www.apple.com/iphone/",
33 | "https://ahrefs.com/blog/",
34 | "ibm.com",
35 | "https://variety.com/",
36 | "https://stackoverflow.com/",
37 | "www.trustpilot.com"
38 | ]`)
39 |     };
40 |   }
41 | 
42 |   async handle(params: any): Promise<any> {
43 |     try {
44 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_referring_domains/live', 'POST', [{
45 |         targets: params.targets
46 |       }]);
47 |       return this.validateAndFormatResponse(response);
48 |     } catch (error) {
49 |       return this.formatErrorResponse(error);
50 |     }
51 |   }
52 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-pages-summary.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkPagesSummaryTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_pages_summary';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "This endpoint will provide you with a comprehensive overview of backlinks and related data for a bulk of up to 1000 pages, domains, or subdomains. If you indicate a single page as a target, you will get comprehensive summary data on all backlinks for that page.";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get summary data for
21 | required field
22 | a domain or a subdomain should be specified without https:// and www.
23 | a page should be specified with absolute URL (including http:// or https://)
24 | you can specify up to 1000 pages, domains, or subdomains in each request.
25 | note that the URLs you set in a single request cannot belong to more than 100 different domains.`),
26 |       include_subdomains: z.boolean().optional().describe(`indicates if indirect links to the target will be included in the results
27 | if set to true, the results will include data on indirect links pointing to a page that either redirects to the target, or points to a canonical page
28 | if set to false, indirect links will be ignored`).default(true)
29 |     };
30 |   }
31 | 
32 |   async handle(params: any): Promise<any> {
33 |     try {
34 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_pages_summary/live', 'POST', [{
35 |         targets: params.targets,
36 |         include_subdomains: params.include_subdomains,
37 |       }]);
38 |       return this.validateAndFormatResponse(response);
39 |     } catch (error) {
40 |       return this.formatErrorResponse(error);
41 |     }
42 |   }
43 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/onpage/tools/instant-pages.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class InstantPagesTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'on_page_instant_pages';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "Using this function you will get page-specific data with detailed information on how well a particular page is optimized for organic search";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       url: z.string().describe("URL to analyze"),
21 |       enable_javascript: z.boolean().optional().describe("Enable JavaScript rendering"),
22 |       custom_js: z.string().optional().describe("Custom JavaScript code to execute"),
23 |       custom_user_agent: z.string().optional().describe("Custom User-Agent header"),
24 |       accept_language: z.string().optional().describe(`language header for accessing the website
25 |         all locale formats are supported (xx, xx-XX, xxx-XX, etc.)
26 |         Note: if you do not specify this parameter, some websites may deny access; in this case, pages will be returned with the "type":"broken in the response array`),
27 |     };
28 |   }
29 | 
30 |   async handle(params: { 
31 |     url: string; 
32 |     enable_javascript?: boolean; 
33 |     custom_js?: string; 
34 |     custom_user_agent?: string; 
35 |     accept_language?: string; 
36 |   }): Promise<any> {
37 |     try {
38 |         const response = await this.dataForSEOClient.makeRequest('/v3/on_page/instant_pages', 'POST', [{
39 |           url: params.url,
40 |           enable_javascript: params.enable_javascript,
41 |           custom_js: params.custom_js,
42 |           custom_user_agent: params.custom_user_agent,
43 |           accept_language: params.accept_language,
44 |         }]);
45 |         return this.validateAndFormatResponse(response);
46 |       } catch (error) {
47 |         return this.formatErrorResponse(error);
48 |       }
49 |   }
50 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-summary.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksSummaryTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_summary';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "This endpoint will provide you with an overview of backlinks data available for a given domain, subdomain, or webpage";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`domain, subdomain or webpage to get backlinks for
21 |         required field
22 | a domain or a subdomain should be specified without https:// and www.
23 | a page should be specified with absolute URL (including http:// or https://)`),
24 |       include_subdomains: z.boolean().optional().describe(`indicates if indirect links to the target will be included in the results
25 | if set to true, the results will include data on indirect links pointing to a page that either redirects to the target, or points to a canonical page
26 | if set to false, indirect links will be ignored`).default(true),
27 |       exclude_internal_backlinks: z.boolean().optional().describe(`indicates if internal backlinks from subdomains to the target will be excluded from the results
28 | if set to true, the results will not include data on internal backlinks from subdomains of the same domain as target
29 | if set to false, internal links will be included in the results`).default(true)
30 |     };
31 |   }
32 | 
33 |   async handle(params: any): Promise<any> {
34 |     try {
35 |       const response = await this.client.makeRequest('/v3/backlinks/summary/live', 'POST', [{
36 |         target: params.target,
37 |         include_subdomains: params.include_subdomains,
38 |         exclude_internal_backlinks: params.exclude_internal_backlinks
39 |       }]);
40 |       return this.validateAndFormatResponse(response);
41 |     } catch (error) {
42 |       return this.formatErrorResponse(error);
43 |     }
44 |   }
45 | } 
```

--------------------------------------------------------------------------------
/src/main/test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
 2 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
 3 | import readline from "readline/promises";
 4 | 
 5 | class MCPClient {
 6 |   private mcp: Client;
 7 |   private transport: StdioClientTransport | null = null;
 8 | 
 9 |   constructor() {
10 |     // Initialize Anthropic client and MCP client
11 |     this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
12 |   }
13 | 
14 |   async connectToServer(serverScriptPath: string) {
15 |     /**
16 |      * Connect to an MCP server
17 |      *
18 |      * @param serverScriptPath - Path to the server script (.py or .js)
19 |      */
20 |     try {
21 |       // Determine script type and appropriate command
22 |       const isJs = serverScriptPath.endsWith(".js");
23 |       const isPy = serverScriptPath.endsWith(".py");
24 |       if (!isJs && !isPy) {
25 |         throw new Error("Server script must be a .js or .py file");
26 |       }
27 |       const command = isPy
28 |         ? process.platform === "win32"
29 |           ? "python"
30 |           : "python3"
31 |         : process.execPath;
32 | 
33 |       // Initialize transport and connect to server
34 |       this.transport = new StdioClientTransport({
35 |         command,
36 |         args: [serverScriptPath],
37 |       });
38 |       this.mcp.connect(this.transport);
39 | 
40 |       // List available tools
41 |       const toolsResult = await this.mcp.listTools();
42 |       console.log(toolsResult);
43 |       console.log(
44 |         "Connected to server with tools:",
45 |         toolsResult.tools.map(({ name }) => name),
46 |       );
47 |     } catch (e) {
48 |       console.log("Failed to connect to MCP server: ", e);
49 |       throw e;
50 |     }
51 |   }
52 | 
53 |   async cleanup() {
54 |     /**
55 |      * Clean up resources
56 |      */
57 |     await this.mcp.close();
58 |   }
59 | }
60 | 
61 | async function main() {
62 |   if (process.argv.length < 3) {
63 |     console.log("Usage: node build/test.js <path_to_server_script>");
64 |     return;
65 |   }
66 |   const mcpClient = new MCPClient();
67 |   try {
68 |     await mcpClient.connectToServer(process.argv[2]);
69 |   } finally {
70 |     await mcpClient.cleanup();
71 |     process.exit(0);
72 |   }
73 | }
74 | 
75 | main();
76 | 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/competitor-research/google-historical-domain-rank-overview.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool, DataForSEOResponse } from '../../../../base.tool.js';
 4 | 
 5 | export class GoogleHistoricalDomainRankOverviewTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'dataforseo_labs_google_historical_rank_overview';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with historical data on rankings and traffic of the specified domain, such as domain ranking distribution in SERPs and estimated monthly traffic volume for both organic and paid results`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`target domain`),
21 |       location_name: z.string().default("United States").describe(`full name of the location
22 | required field
23 | only in format "Country" (not "City" or "Region")
24 | example:
25 | 'United Kingdom', 'United States', 'Canada'`),
26 |       language_code: z.string().default("en").describe(
27 |         `language code
28 |         required field
29 |         example:
30 |         en`),
31 |       ignore_synonyms: z.boolean().default(true).describe(
32 |           `ignore highly similar keywords, if set to true, results will be more accurate`),        
33 |       include_clickstream_data: z.boolean().optional().default(false).describe(
34 |         `Include or exclude data from clickstream-based metrics in the result`)
35 |     };
36 |   }
37 | 
38 |   async handle(params: any): Promise<any> {
39 |     try {
40 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/historical_rank_overview/live', 'POST', [{
41 |         target: params.target,
42 |         location_name: params.location_name,
43 |         language_code: params.language_code,
44 |         ignore_synonyms: params.ignore_synonyms,
45 |         include_clickstream_data: params.include_clickstream_data
46 |       }]);
47 |       return this.validateAndFormatResponse(response);
48 | 
49 |     } catch (error) {
50 |       return this.formatErrorResponse(error);
51 |     }
52 |   }
53 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/tools/serp-youtube-video-info-live-advanced.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | 
 5 | export class SerpYoutubeVideoInfoLiveAdvancedTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'serp_youtube_video_info_live_advanced';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return 'provides data on the video you specify';
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       video_id: z.string().describe("ID of the video"),
21 |       location_name: z.string().describe(`full name of the location
22 | required field
23 | Location format - hierarchical, comma-separated (from most specific to least)
24 |  Can be one of:
25 |  1. Country only: "United States"
26 |  2. Region,Country: "California,United States"
27 |  3. City,Region,Country: "San Francisco,California,United States"`),
28 |       language_code: z.string().describe("search engine language code (e.g., 'en')"),
29 |       device: z.string().default('desktop').optional().describe(`device type
30 | optional field
31 | can take the values:desktop, mobile
32 | default value: desktop`),
33 |       os: z.string().default('windows').optional().describe(`device operating system
34 | optional field
35 | if you specify desktop in the device field, choose from the following values: windows, macos
36 | default value: windows
37 | if you specify mobile in the device field, choose from the following values: android, ios
38 | default value: android`)
39 |     };
40 |   }
41 | 
42 |   async handle(params:any): Promise<any> {
43 |     try {
44 |       console.error(JSON.stringify(params, null, 2));
45 |       const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/video_info/live/advanced`, 'POST', [{
46 |         video_id: params.video_id,
47 |         location_name: params.location_name,
48 |         language_code: params.language_code,
49 |         device: params.device,
50 |         os: params.os,
51 |       }]);
52 |       return this.validateAndFormatResponse(response);
53 |     } catch (error) {
54 |       return this.formatErrorResponse(error);
55 |     }
56 |   }
57 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/tools/serp-organic-locations-list.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | import { DataForSEOResponse } from '../../base.tool.js';
 5 | 
 6 | export class SerpOrganicLocationsListTool extends BaseTool {
 7 |   constructor(dataForSEOClient: DataForSEOClient) {
 8 |     super(dataForSEOClient);
 9 |   }
10 | 
11 |   getName(): string {
12 |     return 'serp_locations';
13 |   }
14 | 
15 |   getDescription(): string {
16 |     return 'Utility tool for serp_organic_live_advanced to get list of availible locations.';
17 |   }
18 |   
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       search_engine: z.string().default('google').describe("search engine name, one of: google, yahoo, bing."),
22 |       country_iso_code: z.string().describe("ISO 3166-1 alpha-2 country code, for example: US, GB, MT"),
23 |       location_type: z.string().optional().describe("Type of location. Possible variants: 'TV Region','Postal Code','Neighborhood','Governorate','National Park','Quarter','Canton','Airport','Okrug','Prefecture','City','Country','Province','Barrio','Sub-District','Congressional District','Municipality District','district','DMA Region','Union Territory','Territory','Colloquial Area','Autonomous Community','Borough','County','State','District','City Region','Commune','Region','Department','Division','Sub-Ward','Municipality','University'"),
24 |       location_name: z.string().optional().describe("Name of location or it`s part.")
25 |     };
26 |   }
27 | 
28 |   async handle(params:any): Promise<any> {
29 |     try {
30 | 
31 |       const payload: Record<string, unknown> = {
32 |         'country_iso_code': params.country_iso_code,
33 |       };
34 | 
35 |       if (params.location_type) {
36 |         payload['location_type'] = params.location_type;
37 |       }
38 |       
39 |       if (params.location_name) {
40 |         payload['location_name'] = params.location_name;
41 |       }
42 | 
43 |       const response = await this.dataForSEOClient.makeRequest(`/v3/serp/${params.search_engine}/locations`, 'POST', [payload]);
44 |       return this.validateAndFormatResponse(response);
45 |     } catch (error) {
46 |       return this.formatErrorResponse(error);
47 |     }
48 |   }
49 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/tools/serp-youtube-locations-list.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | import { DataForSEOResponse } from '../../base.tool.js';
 5 | 
 6 | 
 7 | export class SerpYoutubeLocationsListTool extends BaseTool {
 8 |   constructor(dataForSEOClient: DataForSEOClient) {
 9 |     super(dataForSEOClient);
10 |   }
11 | 
12 |   getName(): string {
13 |     return 'serp_youtube_locations';
14 |   }
15 | 
16 |   getDescription(): string {
17 |     return 'Utility tool to get list of available locations for: serp_youtube_organic_live_advanced, serp_youtube_video_info_live_advanced, serp_youtube_video_comments_live_advanced, serp_youtube_video_subtitles_live_advanced.';
18 |   }
19 | 
20 |   getParams(): z.ZodRawShape {
21 |     return {
22 |       country_iso_code: z.string().describe("ISO 3166-1 alpha-2 country code, for example: US, GB, MT"),
23 |       location_type: z.string().optional().describe("Type of location. Possible variants: 'TV Region','Postal Code','Neighborhood','Governorate','National Park','Quarter','Canton','Airport','Okrug','Prefecture','City','Country','Province','Barrio','Sub-District','Congressional District','Municipality District','district','DMA Region','Union Territory','Territory','Colloquial Area','Autonomous Community','Borough','County','State','District','City Region','Commune','Region','Department','Division','Sub-Ward','Municipality','University'"),
24 |       location_name: z.string().optional().describe("Name of location or it`s part.")
25 |     };
26 |   }
27 | 
28 |   async handle(params:any): Promise<any> {
29 |     try {
30 | 
31 |      const payload: Record<string, unknown> = {
32 |         'country_iso_code': params.country_iso_code,
33 |       };
34 | 
35 |       if (params.location_type) {
36 |         payload['location_type'] = params.location_type;
37 |       }
38 | 
39 |       if (params.location_name) {
40 |         payload['location_name'] = params.location_name;
41 |       }
42 | 
43 |       const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/locations`, 'POST', [payload]);
44 |       return this.validateAndFormatResponse(response);
45 |     } catch (error) {
46 |       return this.formatErrorResponse(error);
47 |     }
48 |   }
49 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/competitor-research/google-bulk-traffic-estimation.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../../../base.tool.js';
 4 | 
 5 | export class GoogleBulkTrafficEstimationTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'dataforseo_labs_bulk_traffic_estimation';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with estimated monthly traffic volumes for up to 1,000 domains, subdomains, or webpages. Along with organic search traffic estimations, you will also get separate values for paid search, featured snippet, and local pack results.`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       targets: z.array(z.string()).describe(`target domains, subdomains, and webpages.
21 |         you can specify domains, subdomains, and webpages in this field;
22 | domains and subdomains should be specified without https:// and www.;
23 | pages should be specified with absolute URL, including https:// and www.;
24 | you can set up to 1000 domains, subdomains or webpages`),
25 |       location_name: z.string().default("United States").describe(`full name of the location
26 | required field
27 | only in format "Country" (not "City" or "Region")
28 | example:
29 | 'United Kingdom', 'United States', 'Canada'`),
30 |       language_code: z.string().default("en").describe(
31 |         `language code
32 |         required field
33 |         example:
34 |         en`),
35 |       ignore_synonyms: z.boolean().default(true).describe(
36 |   `ignore highly similar keywords, if set to true, results will be more accurate`),
37 | 
38 |     };
39 |   }
40 | 
41 |   async handle(params: any): Promise<any> {
42 |     try {
43 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/bulk_traffic_estimation/live', 'POST', [{
44 |         targets: params.targets,
45 |         location_name: params.location_name,
46 |         language_code: params.language_code,
47 |         item_types: ['organic'],
48 |         ignore_synonyms: params.ignore_synonyms
49 |       }]);
50 |       return this.validateAndFormatResponse(response);
51 |     } catch (error) {
52 |       return this.formatErrorResponse(error);
53 |     }
54 |   }
55 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/serp-api.module.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { BaseModule, ToolDefinition } from '../base.module.js';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | import { z } from 'zod';
 4 | import { SerpOrganicLiveAdvancedTool } from './tools/serp-organic-live-advanced.tool.js';
 5 | import { SerpOrganicLocationsListTool } from './tools/serp-organic-locations-list.tool.js';
 6 | import { SerpYoutubeOrganicLiveAdvancedTool } from './tools/serp-youtube-organic-live-advanced.tool.js';
 7 | import { SerpYoutubeVideoInfoLiveAdvancedTool } from './tools/serp-youtube-video-info-live-advanced.tool.js';
 8 | import { SerpYoutubeVideoCommentsLiveAdvancedTool } from './tools/serp-youtube-video-comments-live-advanced-tool.js';
 9 | import { SerpYoutubeVideoSubtitlesLiveAdvancedTool } from './tools/serp-youtube-video-subtitles-live-advanced-tool.js';
10 | import { SerpYoutubeLocationsListTool } from './tools/serp-youtube-locations-list.tool.js';
11 | import { serpPrompts } from './serp.prompt.js';
12 | 
13 | export class SerpApiModule extends BaseModule {
14 |   getTools(): Record<string, ToolDefinition> {
15 |     const tools = [
16 |       new SerpOrganicLiveAdvancedTool(this.dataForSEOClient),
17 |       new SerpOrganicLocationsListTool(this.dataForSEOClient),
18 | 
19 |       new SerpYoutubeLocationsListTool(this.dataForSEOClient),
20 |       new SerpYoutubeOrganicLiveAdvancedTool(this.dataForSEOClient),
21 |       new SerpYoutubeVideoInfoLiveAdvancedTool(this.dataForSEOClient),
22 |       new SerpYoutubeVideoCommentsLiveAdvancedTool(this.dataForSEOClient),
23 |       new SerpYoutubeVideoSubtitlesLiveAdvancedTool(this.dataForSEOClient),
24 |       // Add more tools here
25 |     ];
26 | 
27 |     return tools.reduce((acc, tool) => ({
28 |       ...acc,
29 |       [tool.getName()]: {
30 |         description: tool.getDescription(),
31 |         params: tool.getParams(),
32 |         handler: (params: any) => tool.handle(params),
33 |       },
34 |     }), {});
35 |   }
36 | 
37 |   getPrompts(): Record<string, PromptDefinition> {
38 |     return serpPrompts.reduce((acc, prompt) => ({
39 |       ...acc,
40 |       [prompt.name]: {
41 |         description: prompt.description,
42 |         params: prompt.params,
43 |         handler: (params: any) => {
44 | 
45 |           return prompt.handler(params);
46 |         },
47 |       },
48 |     }), {});
49 |   }
50 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/keyword-research/google-historical-keyword-data.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool, DataForSEOResponse } from '../../../../base.tool.js';
 4 | 
 5 | export class GoogleHistoricalKeywordDataTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'dataforseo_labs_google_historical_keyword_data';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint provides Google historical keyword data for specified keywords, including search volume, cost-per-click, competition values for paid search, monthly searches, and search volume trends. You can get historical keyword data since August, 2021, depending on keywords along with location and language combination`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       keywords: z.array(z.string()).describe(`keywords
21 | required field
22 | The maximum number of keywords you can specify: 700
23 | The maximum number of characters for each keyword: 80
24 | The maximum number of words for each keyword phrase: 10
25 | the specified keywords will be converted to lowercase format, data will be provided in a separate array
26 | note that if some of the keywords specified in this array are omitted in the results you receive, then our database doesn't contain such keywords and cannot return data on them
27 | you will not be charged for the keywords omitted in the results`),
28 |       location_name: z.string().default("United States").describe(`full name of the location
29 | required field
30 | only in format "Country" (not "City" or "Region")
31 | example:
32 | 'United Kingdom', 'United States', 'Canada'`),
33 |       language_code: z.string().default("en").describe(
34 |         `language code
35 |         required field
36 |         example:
37 |         en`)
38 |     };
39 |   }
40 | 
41 |   async handle(params: any): Promise<any> {
42 |     try {
43 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/historical_keyword_data/live', 'POST', [{
44 |         keywords: params.keywords,
45 |         location_name: params.location_name,
46 |         language_code: params.language_code
47 |       }]);
48 |       return this.validateAndFormatResponse(response);
49 |     } catch (error) {
50 |       return this.formatErrorResponse(error);
51 |     }
52 |   }
53 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-ranks.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkRanksTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_ranks';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "This endpoint will provide you with rank scores of the domains, subdomains, and pages specified in the targets array. The score is based on the number of referring domains pointing to the specified domains, subdomains, or pages. The rank values represent real-time data for the date of the request and range from 0 (no backlinks detected) to 1,000 (highest rank). A similar scoring system is used in Google’s Page Rank algorithm";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
21 | required field
22 | you can set up to 1000 domains, subdomains or webpages
23 | the domain or subdomain should be specified without https:// and www.
24 | the page should be specified with absolute URL (including http:// or https://)
25 | example:
26 | "targets": [
27 | "forbes.com",
28 | "cnn.com",
29 | "bbc.com",
30 | "yelp.com",
31 | "https://www.apple.com/iphone/",
32 | "https://ahrefs.com/blog/",
33 | "ibm.com",
34 | "https://variety.com/",
35 | "https://stackoverflow.com/",
36 | "www.trustpilot.com"
37 | ]`),
38 |       rank_scale: z.string().optional().describe(`defines the scale used for calculating and displaying the rank, domain_from_rank, and page_from_rank values
39 | optional field
40 | you can use this parameter to choose whether rank values are presented on a 0–100 or 0–1000 scale
41 | possible values:
42 | one_hundred — rank values are displayed on a 0–100 scale
43 | one_thousand — rank values are displayed on a 0–1000 scale`).default('one_thousand')
44 |     };
45 |   }
46 | 
47 |   async handle(params: any): Promise<any> {
48 |     try {
49 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_ranks/live', 'POST', [{
50 |         targets: params.targets,
51 |         rank_scale: params.rank_scale        
52 |       }]);
53 |       return this.validateAndFormatResponse(response);
54 |     } catch (error) {
55 |       return this.formatErrorResponse(error);
56 |     }
57 |   }
58 | } 
```

--------------------------------------------------------------------------------
/src/main/init-mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { DataForSEOClient, DataForSEOConfig } from "../core/client/dataforseo.client.js";
 3 | import { EnabledModulesSchema } from "../core/config/modules.config.js";
 4 | import { ModuleLoaderService } from "../core/utils/module-loader.js";
 5 | import { BaseModule, ToolDefinition } from "../core/modules/base.module.js";
 6 | import { z } from 'zod';
 7 | import { name, version } from '../core/utils/version.js';
 8 | 
 9 | 
10 | export function initMcpServer(username: string | undefined, password: string | undefined): McpServer {
11 |   const server = new McpServer({
12 |     name,
13 |     version,
14 |   }, { capabilities: { logging: {} } });
15 | 
16 |   // Initialize DataForSEO client
17 |   const dataForSEOConfig: DataForSEOConfig = {
18 |     username: username || "",
19 |     password: password || "",
20 |   };
21 |   
22 |   const dataForSEOClient = new DataForSEOClient(dataForSEOConfig);
23 |   console.error('DataForSEO client initialized');
24 |   
25 |   // Parse enabled modules from environment
26 |   const enabledModules = EnabledModulesSchema.parse(process.env.ENABLED_MODULES);
27 |   
28 |   // Initialize modules
29 |   const modules: BaseModule[] = ModuleLoaderService.loadModules(dataForSEOClient, enabledModules);
30 |   
31 |   const enabledPrompts = process.env.ENABLED_PROMPTS?.split(',').map(name => name.trim()) || [];
32 | 
33 |   // Register modules
34 |   modules.forEach(module => {
35 |     
36 |     const tools = module.getTools();
37 |     Object.entries(tools).forEach(([name, tool]) => {
38 |       const typedTool = tool as ToolDefinition;
39 |       const schema = z.object(typedTool.params);
40 |       server.tool(
41 |         name,
42 |         typedTool.description,
43 |         schema.shape,
44 |         typedTool.handler
45 |       );
46 |     });
47 | 
48 |     const prompts = module.getPrompts();
49 |     // Filter prompts based on enabledPrompts configuration
50 |     const allowedPrompts = enabledPrompts.length === 0
51 |         ? prompts
52 |         : Object.fromEntries(Object.entries(prompts).filter(([promptName]) => enabledPrompts.includes(promptName)));
53 | 
54 |     Object.entries(allowedPrompts).forEach(([name, prompt]) => {
55 |       server.registerPrompt(
56 |         name,
57 |         {
58 |           description: prompt.description,
59 |           argsSchema: prompt.params,
60 |         },
61 |         prompt.handler
62 |       );
63 |     });
64 |   });
65 | 
66 | 
67 |   return server;
68 | }
```

--------------------------------------------------------------------------------
/src/core/modules/serp/tools/serp-youtube-organic-live-advanced.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | 
 5 | export class SerpYoutubeOrganicLiveAdvancedTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'serp_youtube_organic_live_advanced';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return 'provides top 20 blocks of youtube search engine results for a keyword';
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       keyword: z.string().describe("Search keyword"),
21 |       location_name: z.string().describe(`full name of the location
22 | required field
23 | Location format - hierarchical, comma-separated (from most specific to least)
24 |  Can be one of:
25 |  1. Country only: "United States"
26 |  2. Region,Country: "California,United States"
27 |  3. City,Region,Country: "San Francisco,California,United States"`),
28 |       language_code: z.string().describe("search engine language code (e.g., 'en')"),
29 |       device: z.string().default('desktop').optional().describe(`device type
30 | optional field
31 | can take the values:desktop, mobile
32 | default value: desktop`),
33 |       os: z.string().default('windows').optional().describe(`device operating system
34 | optional field
35 | if you specify desktop in the device field, choose from the following values: windows, macos
36 | default value: windows
37 | if you specify mobile in the device field, choose from the following values: android, ios
38 | default value: android`),
39 |       block_depth: z.number().default(20).optional().describe(`parsing depth
40 | optional field
41 | number of blocks of results in SERP
42 | max value: 700`)
43 |     };
44 |   }
45 | 
46 |   async handle(params:any): Promise<any> {
47 |     try {
48 |       console.error(JSON.stringify(params, null, 2));
49 |       const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/organic/live/advanced`, 'POST', [{
50 |         keyword: params.keyword,
51 |         location_name: params.location_name,
52 |         language_code: params.language_code,
53 |         device: params.device,
54 |         os: params.os,
55 |         block_depth: params.block_depth,
56 |       }]);
57 |       return this.validateAndFormatResponse(response);
58 |     } catch (error) {
59 |       return this.formatErrorResponse(error);
60 |     }
61 |   }
62 | } 
63 | 
64 | 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/keyword-research/google-keyword-overview.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool, DataForSEOResponse } from '../../../../base.tool.js';
 4 | 
 5 | export class GoogleKeywordOverviewTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'dataforseo_labs_google_keyword_overview';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint provides Google keyword data for specified keywords. For each keyword, you will receive current cost-per-click, competition values for paid search, search volume, search intent, monthly searches`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       keywords: z.array(z.string()).describe(`keywords
21 | required field
22 | The maximum number of keywords you can specify: 700
23 | The maximum number of characters for each keyword: 80
24 | The maximum number of words for each keyword phrase: 10
25 | the specified keywords will be converted to lowercase format, data will be provided in a separate array
26 | note that if some of the keywords specified in this array are omitted in the results you receive, then our database doesn't contain such keywords and cannot return data on them
27 | you will not be charged for the keywords omitted in the results`),
28 |       location_name: z.string().default("United States").describe(`full name of the location
29 | required field
30 | only in format "Country" (not "City" or "Region")
31 | example:
32 | 'United Kingdom', 'United States', 'Canada'`),
33 |       language_code: z.string().default("en").describe(
34 |         `language code
35 |         required field
36 |         example:
37 |         en`),
38 |       include_clickstream_data: z.boolean().optional().default(false).describe(
39 |         `Include or exclude data from clickstream-based metrics in the result`)
40 |     };
41 |   }
42 | 
43 |   async handle(params: any): Promise<any> {
44 |     try {
45 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/keyword_overview/live', 'POST', [{
46 |         keywords: params.keywords,
47 |         location_name: params.location_name,
48 |         language_code: params.language_code,
49 |         include_clickstream_data: params.include_clickstream_data
50 |       }]);
51 |       return this.validateAndFormatResponse(response);
52 |     } catch (error) {
53 |       return this.formatErrorResponse(error);
54 |     }
55 |   }
56 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-new-lost-referring-domains.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkNewLostReferringDomainsTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_new_lost_referring_domains';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the number of referring domains pointing to the domains, subdomains and pages specified in the targets array.
16 | Note that if you indicate a domain as a target, you will get result for the root domain (domain with all of its subdomains), e.g. dataforseo.com and app.dataforseo.com`;
17 |   }
18 | 
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
22 | required field
23 | you can set up to 1000 domains, subdomains or webpages
24 | the domain or subdomain should be specified without https:// and www.
25 | the page should be specified with absolute URL (including http:// or https://)
26 | example:
27 | "targets": [
28 | "forbes.com",
29 | "cnn.com",
30 | "bbc.com",
31 | "yelp.com",
32 | "https://www.apple.com/iphone/",
33 | "https://ahrefs.com/blog/",
34 | "ibm.com",
35 | "https://variety.com/",
36 | "https://stackoverflow.com/",
37 | "www.trustpilot.com"
38 | ]`),
39 | date_from: z.string().optional().describe(`starting date of the time range
40 | optional field
41 | this field indicates the date which will be used as a threshold for new and lost backlinks;
42 | the backlinks that appeared in our index after the specified date will be considered as new;
43 | the backlinks that weren’t found after the specified date, but were present before, will be considered as lost;
44 | default value: today’s date -(minus) one month;
45 | e.g. if today is 2021-10-13, default date_from will be 2021-09-13.
46 | minimum value equals today’s date -(minus) one year;
47 | e.g. if today is 2021-10-13, minimum date_from will be 2020-10-13.
48 | 
49 | date format: "yyyy-mm-dd"
50 | example:
51 | "2021-01-01"`)
52 |     };
53 |   }
54 | 
55 |   async handle(params: any): Promise<any> {
56 |     try {
57 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_new_lost_referring_domains/live', 'POST', [{
58 |         targets: params.targets
59 |       }]);
60 |       return this.validateAndFormatResponse(response);
61 |     } catch (error) {
62 |       return this.formatErrorResponse(error);
63 |     }
64 |   }
65 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/tools/serp-youtube-video-comments-live-advanced-tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | 
 5 | export class SerpYoutubeVideoCommentsLiveAdvancedTool extends BaseTool {
 6 |     constructor(dataForSEOClient: DataForSEOClient) {
 7 |         super(dataForSEOClient);
 8 |     }
 9 | 
10 |     getName(): string {
11 |         return 'serp_youtube_video_comments_live_advanced';
12 |     }
13 | 
14 |     getDescription(): string {
15 |         return 'provides data on the video comments you specify';
16 |     }
17 | 
18 |     getParams(): z.ZodRawShape {
19 |         return {
20 |             video_id: z.string().describe("ID of the video"),
21 |             location_name: z.string().describe(`full name of the location
22 | required field
23 | Location format - hierarchical, comma-separated (from most specific to least)
24 |  Can be one of:
25 |  1. Country only: "United States"
26 |  2. Region,Country: "California,United States"
27 |  3. City,Region,Country: "San Francisco,California,United States"`),
28 |             language_code: z.string().describe("search engine language code (e.g., 'en')"),
29 |             device: z.string().default('desktop').optional().describe(`device type
30 | optional field
31 | can take the values:desktop, mobile
32 | default value: desktop`),
33 |             os: z.string().default('windows').optional().describe(`device operating system
34 | optional field
35 | if you specify desktop in the device field, choose from the following values: windows, macos
36 | default value: windows
37 | if you specify mobile in the device field, choose from the following values: android, ios
38 | default value: android`),
39 |             depth: z.number().default(20).optional().describe(`parsing depth, number of results in SERP, max value: 700`)
40 |         };
41 |     }
42 | 
43 |     async handle(params: any): Promise<any> {
44 |         try {
45 |             console.error(JSON.stringify(params, null, 2));
46 |             const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/video_comments/live/advanced`, 'POST', [{
47 |                 video_id: params.video_id,
48 |                 location_name: params.location_name,
49 |                 language_code: params.language_code,
50 |                 device: params.device,
51 |                 os: params.os,
52 |                 depth: params.depth,
53 |             }]);
54 |             return this.validateAndFormatResponse(response);
55 |         } catch (error) {
56 |             return this.formatErrorResponse(error);
57 |         }
58 |     }
59 | }
```

--------------------------------------------------------------------------------
/src/core/modules/onpage/tools/content-parsing.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | import { DataForSEOResponse } from '../../base.tool.js';
 5 | import { defaultGlobalToolConfig } from '../../../config/global.tool.js';
 6 | 
 7 | export class ContentParsingTool extends BaseTool {
 8 |   constructor(dataForSEOClient: DataForSEOClient) {
 9 |     super(dataForSEOClient);
10 |   }
11 | 
12 |   getName(): string {
13 |     return 'on_page_content_parsing';
14 |   }
15 | 
16 |   getDescription(): string {
17 |     return 'This endpoint allows parsing the content on any page you specify and will return the structured content of the target page, including link URLs, anchors, headings, and textual content.';
18 |   }
19 | 
20 |   getParams(): z.ZodRawShape {
21 |     return {
22 |       url: z.string().describe("URL of the page to parse"),
23 |       enable_javascript: z.boolean().optional().describe("Enable JavaScript rendering"),
24 |       custom_js: z.string().optional().describe("Custom JavaScript code to execute"),
25 |       custom_user_agent: z.string().optional().describe("Custom User-Agent header"),
26 |       accept_language: z.string().optional().describe("Accept-Language header value"),
27 |     };
28 |   }
29 | 
30 |   async handle(params: { 
31 |     url: string; 
32 |     enable_javascript?: boolean; 
33 |     custom_js?: string; 
34 |     custom_user_agent?: string; 
35 |     accept_language?: string; 
36 |   }): Promise<any> {
37 |     try {
38 |       const response = await this.dataForSEOClient.makeRequest('/v3/on_page/content_parsing/live', 'POST', [{
39 |         url: params.url,
40 |         enable_javascript: params.enable_javascript,
41 |         custom_js: params.custom_js,
42 |         custom_user_agent: params.custom_user_agent,
43 |         accept_language: params.accept_language,
44 |         markdown_view: true
45 |       }]);
46 |       console.error(JSON.stringify(response));
47 |       if(defaultGlobalToolConfig.fullResponse || this.supportOnlyFullResponse()){
48 |         let data = response as DataForSEOFullResponse;
49 |         this.validateResponseFull(data);
50 |         let result = data.tasks[0].result;
51 |         return this.formatResponse(result);
52 |       }
53 |       else{
54 |         let data = response as DataForSEOResponse;
55 |         this.validateResponse(data);
56 |         let result = data.items[0].page_as_markdown;
57 |         return this.formatResponse(result);
58 |       }
59 |     } catch (error) {
60 |       return this.formatErrorResponse(error);
61 |     }
62 |   }
63 | } 
```

--------------------------------------------------------------------------------
/src/core/utils/module-loader.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { DataForSEOClient } from '../client/dataforseo.client.js';
 2 | import { SerpApiModule } from '../modules/serp/serp-api.module.js';
 3 | import { AiOptimizationApiModule } from '../modules/ai-optimization/ai-optimization-api-module.js'
 4 | import { KeywordsDataApiModule } from '../modules/keywords-data/keywords-data-api.module.js';
 5 | import { OnPageApiModule } from '../modules/onpage/onpage-api.module.js';
 6 | import { DataForSEOLabsApi } from '../modules/dataforseo-labs/dataforseo-labs-api.module.js';
 7 | import { BacklinksApiModule } from '../modules/backlinks/backlinks-api.module.js';
 8 | import { BusinessDataApiModule } from '../modules/business-data-api/business-data-api.module.js';
 9 | import { DomainAnalyticsApiModule } from '../modules/domain-analytics/domain-analytics-api.module.js';
10 | import { BaseModule } from '../modules/base.module.js';
11 | import { EnabledModules, isModuleEnabled } from '../config/modules.config.js';
12 | import { ContentAnalysisApiModule } from '../modules/content-analysis/content-analysis-api.module.js';
13 | 
14 | export class ModuleLoaderService {
15 |   static loadModules(dataForSEOClient: DataForSEOClient, enabledModules: EnabledModules): BaseModule[] {
16 |     const modules: BaseModule[] = [];
17 | 
18 |     if (isModuleEnabled('AI_OPTIMIZATION', enabledModules)) {
19 |       modules.push(new AiOptimizationApiModule(dataForSEOClient));
20 |     }
21 |     if (isModuleEnabled('SERP', enabledModules)) {
22 |       modules.push(new SerpApiModule(dataForSEOClient));
23 |     }
24 |     if (isModuleEnabled('KEYWORDS_DATA', enabledModules)) {
25 |       modules.push(new KeywordsDataApiModule(dataForSEOClient));
26 |     }
27 |     if (isModuleEnabled('ONPAGE', enabledModules)) {
28 |       modules.push(new OnPageApiModule(dataForSEOClient));
29 |     }
30 |     if (isModuleEnabled('DATAFORSEO_LABS', enabledModules)) {
31 |       modules.push(new DataForSEOLabsApi(dataForSEOClient));
32 |     }
33 |     if (isModuleEnabled('BACKLINKS', enabledModules)) {
34 |       modules.push(new BacklinksApiModule(dataForSEOClient));
35 |     }
36 |     if (isModuleEnabled('BUSINESS_DATA', enabledModules)) {
37 |       modules.push(new BusinessDataApiModule(dataForSEOClient));
38 |     }
39 |     if (isModuleEnabled('DOMAIN_ANALYTICS', enabledModules)) {
40 |       modules.push(new DomainAnalyticsApiModule(dataForSEOClient));
41 |     }
42 |     if(isModuleEnabled('CONTENT_ANALYSIS', enabledModules)) {
43 |       modules.push(new ContentAnalysisApiModule(dataForSEOClient));
44 |     }
45 | 
46 |     return modules;
47 |   }
48 | }
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/competitor-research/google-historical-serp.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
 3 | import { BaseTool, DataForSEOFullResponse, DataForSEOResponse } from '../../../../base.tool.js';
 4 | import { defaultGlobalToolConfig } from '../../../../../config/global.tool.js';
 5 | 
 6 | export class GoogleHistoricalSERP extends BaseTool {
 7 |   constructor(private client: DataForSEOClient) {
 8 |     super(client);
 9 |   }
10 | 
11 |   getName(): string {
12 |     return 'dataforseo_labs_google_historical_serp';
13 |   }
14 | 
15 |   getDescription(): string {
16 |     return `This endpoint will provide you with Google SERPs collected within the specified time frame. You will also receive a complete overview of featured snippets and other extra elements that were present within the specified dates. The data will allow you to analyze the dynamics of keyword rankings over time for the specified keyword and location.`;
17 |   }
18 | 
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       keyword: z.string().describe(`target keyword`),
22 |       location_name: z.string().default("United States").describe(`full name of the location
23 | required field
24 | only in format "Country" (not "City" or "Region")
25 | example:
26 | 'United Kingdom', 'United States', 'Canada'`),
27 |       language_code: z.string().default("en").describe(
28 |         `language code
29 |         required field
30 |         example:
31 |         en`)
32 |     };
33 |   }
34 | 
35 |   async handle(params: any): Promise<any> {
36 |     try {
37 |       const response = await this.client.makeRequest('/v3/dataforseo_labs/google/historical_serps/live', 'POST', [{
38 |         keyword: params.keyword,
39 |         location_name: params.location_name,
40 |         language_code: params.language_code
41 |       }]);
42 | 
43 |       console.error(JSON.stringify(response));
44 |       if(defaultGlobalToolConfig.fullResponse || this.supportOnlyFullResponse()){
45 |         return this.validateAndFormatResponse(response);
46 |       }
47 |       else {
48 |         let data = response as DataForSEOResponse;
49 |         this.validateResponse(data);
50 |         let result = data.items;
51 |         let filteredResult = result.map(item => this.filterResponseFields(item, [     
52 |           "datetime",
53 |           "items.type",
54 |           "items.title",
55 |           "items.domain",
56 |           "items.rank_absolute"]));
57 |           console.error(JSON.stringify(filteredResult));
58 |         return this.formatResponse(filteredResult);
59 |       }
60 |       } catch (error) {
61 |       return this.formatErrorResponse(error);
62 |     }
63 |   }
64 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/tools/serp-youtube-video-subtitles-live-advanced-tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | 
 5 | export class SerpYoutubeVideoSubtitlesLiveAdvancedTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'serp_youtube_video_subtitles_live_advanced';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return 'provides data on the video subtitles you specify';
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       video_id: z.string().describe("ID of the video"),
21 |       location_name: z.string().describe(`full name of the location
22 | required field
23 | Location format - hierarchical, comma-separated (from most specific to least)
24 |  Can be one of:
25 |  1. Country only: "United States"
26 |  2. Region,Country: "California,United States"
27 |  3. City,Region,Country: "San Francisco,California,United States"`),
28 |       language_code: z.string().describe("search engine language code (e.g., 'en')"),
29 |       subtitles_language: z.string().optional().describe("language code of original text (e.g., 'en')"),
30 |       subtitles_translate_language: z.string().optional().describe("language code of translated text (e.g., 'en')"),
31 |       device: z.string().default('desktop').optional().describe(`device type
32 | optional field
33 | can take the values:desktop, mobile
34 | default value: desktop`),
35 |       os: z.string().default('windows').optional().describe(`device operating system
36 | optional field
37 | if you specify desktop in the device field, choose from the following values: windows, macos
38 | default value: windows
39 | if you specify mobile in the device field, choose from the following values: android, ios
40 | default value: android`)
41 |     };
42 |   }
43 | 
44 |   async handle(params:any): Promise<any> {
45 |     try {
46 |       console.error(JSON.stringify(params, null, 2));
47 |       const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/video_subtitles/live/advanced`, 'POST', [{
48 |         video_id: params.video_id,
49 |         location_name: params.location_name,
50 |         language_code: params.language_code,
51 |         subtitles_language: params.subtitles_language,
52 |         subtitles_translate_language: params.subtitles_translate_language,
53 |         device: params.device,
54 |         os: params.os,
55 |       }]);
56 |       return this.validateAndFormatResponse(response);
57 |     } catch (error) {
58 |       return this.formatErrorResponse(error);
59 |     }
60 |   }
61 | } 
62 | 
63 | 
64 | 
```

--------------------------------------------------------------------------------
/src/core/modules/keywords-data/tools/dataforseo-trends/dataforseo-trends-subregion-interests.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 4 | 
 5 | export class DataForSeoTrendsSubregionInterestsTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'keywords_data_dataforseo_trends_subregion_interests';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with location-specific keyword popularity data from DataForSEO Trends`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       location_name: z.string().nullable().default(null).describe(`full name of the location
21 |         optional field
22 |         in format "Country"
23 |         example:
24 |         United Kingdom`),
25 |       keywords: z.array(z.string()).describe(`keywords
26 |         the maximum number of keywords you can specify: 5`),
27 |       type: z.enum(['web', 'news', 'ecommerce']).default('web').describe(`dataforseo trends type`),
28 |       date_from: z.string().optional().describe(`starting date of the time range
29 |           if you don’t specify this field, the current day and month of the preceding year will be used by default
30 |           minimal value for the web type: 2004-01-01
31 |           minimal value for other types: 2008-01-01
32 |           date format: "yyyy-mm-dd"
33 |           example:
34 |           "2019-01-15"`),
35 |       date_to: z.string().optional()
36 |           .describe(
37 |             `ending date of the time range
38 |             if you don’t specify this field, the today’s date will be used by default
39 |             date format: "yyyy-mm-dd"
40 |             example:
41 |             "2019-01-15"`),
42 |       time_range: z.enum(['past_4_hours', 'past_day', 'past_7_days', 'past_30_days', 'past_90_days', 'past_12_months', 'past_5_years'])
43 |           .default('past_7_days')
44 |           .describe(
45 |             `preset time ranges
46 |             if you specify date_from or date_to parameters, this field will be ignored when setting a task`),
47 |     };
48 |   }
49 | 
50 |   async handle(params: any): Promise<any> {
51 |     try {
52 |       const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/dataforseo_trends/subregion_interests/live', 'POST', [{
53 |         location_name: params.location_name,
54 |         keywords: params.keywords,
55 |         type: params.type,
56 |         date_from: params.date_from,
57 |         date_to: params.date_to,
58 |         time_range: params.time_range,
59 |       }]);
60 |       return this.validateAndFormatResponse(response);
61 |     } catch (error) {
62 |       return this.formatErrorResponse(error);
63 |     }
64 |   }
65 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-bulk-new-lost-backlinks.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksBulkNewLostBacklinksTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_bulk_new_lost_backlinks';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the number of referring domains pointing to domains, subdomains, and pages specified in the targets array. The returned numbers are based on all live referring domains, that is, total number of domains pointing to the target with any type of backlinks (e.g., nofollow, noreferrer, ugc, sponsored etc) that were found during the latest check.
16 | Note that if you indicate a domain as a target, you will get result for the root domain (domain with all of its subdomains), e.g. dataforseo.com and app.dataforseo.com`;
17 |   }
18 | 
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
22 | required field
23 | you can set up to 1000 domains, subdomains or webpages
24 | the domain or subdomain should be specified without https:// and www.
25 | the page should be specified with absolute URL (including http:// or https://)
26 | example:
27 | "targets": [
28 | "forbes.com",
29 | "cnn.com",
30 | "bbc.com",
31 | "yelp.com",
32 | "https://www.apple.com/iphone/",
33 | "https://ahrefs.com/blog/",
34 | "ibm.com",
35 | "https://variety.com/",
36 | "https://stackoverflow.com/",
37 | "www.trustpilot.com"
38 | ]`),
39 | date_from: z.string().optional().describe(`starting date of the time range
40 | optional field
41 | this field indicates the date which will be used as a threshold for new and lost backlinks;
42 | the backlinks that appeared in our index after the specified date will be considered as new;
43 | the backlinks that weren’t found after the specified date, but were present before, will be considered as lost;
44 | default value: today’s date -(minus) one month;
45 | e.g. if today is 2021-10-13, default date_from will be 2021-09-13.
46 | minimum value equals today’s date -(minus) one year;
47 | e.g. if today is 2021-10-13, minimum date_from will be 2020-10-13.
48 | 
49 | date format: "yyyy-mm-dd"
50 | example:
51 | "2021-01-01"`)
52 |     };
53 |   }
54 | 
55 |   async handle(params: any): Promise<any> {
56 |     try {
57 |       const response = await this.client.makeRequest('/v3/backlinks/bulk_new_lost_backlinks/live', 'POST', [{
58 |         targets: params.targets
59 |       }]);
60 |       return this.validateAndFormatResponse(response);
61 |     } catch (error) {
62 |       return this.formatErrorResponse(error);
63 |     }
64 |   }
65 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/keywords-data/tools/dataforseo-trends/dataforseo-trends-demography.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 4 | 
 5 | export class DataForSeoTrendsDemographyTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'keywords_data_dataforseo_trends_demography';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the demographic breakdown (by age and gender) of keyword popularity per each specified term based on DataForSEO Trends data`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       location_name: z.string().nullable().default(null).describe(`full name of the location
21 |         optional field
22 |         in format "Country"
23 |         example:
24 |         United Kingdom`),
25 |       keywords: z.array(z.string()).describe(`keywords
26 |         the maximum number of keywords you can specify: 5`),
27 |       type: z.enum(['web', 'news', 'ecommerce']).default('web').describe(`dataforseo trends type`),
28 |       date_from: z.string().optional().describe(`starting date of the time range
29 |           if you don’t specify this field, the current day and month of the preceding year will be used by default
30 |           minimal value for the web type: 2004-01-01
31 |           minimal value for other types: 2008-01-01
32 |           date format: "yyyy-mm-dd"
33 |           example:
34 |           "2019-01-15"`),
35 |       date_to: z.string().optional()
36 |           .describe(
37 |             `ending date of the time range
38 |             if you don’t specify this field, the today’s date will be used by default
39 |             date format: "yyyy-mm-dd"
40 |             example:
41 |             "2019-01-15"`),
42 |       time_range: z.enum(['past_4_hours', 'past_day', 'past_7_days', 'past_30_days', 'past_90_days', 'past_12_months', 'past_5_years'])
43 |           .default('past_7_days')
44 |           .describe(
45 |             `preset time ranges
46 |             if you specify date_from or date_to parameters, this field will be ignored when setting a task`),
47 |     };
48 |   }
49 | 
50 |   async handle(params: any): Promise<any> {
51 |     try {
52 |       const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/dataforseo_trends/demography/live', 'POST', [{
53 |         location_name: params.location_name,
54 |         keywords: params.keywords,
55 |         type: params.type,
56 |         date_from: params.date_from,
57 |         date_to: params.date_to,
58 |         time_range: params.time_range,
59 |       }]);
60 |       return this.validateAndFormatResponse(response);
61 |     } catch (error) {
62 |       return this.formatErrorResponse(error);
63 |     }
64 |   }
65 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/keywords-data/tools/dataforseo-trends/dataforseo-trends-explore.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 4 | 
 5 | export class DataForSeoTrendsExploreTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'keywords_data_dataforseo_trends_explore';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the keyword popularity data from DataForSEO Trends. You can check keyword trends for Google Search, Google News, and Google Shopping`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       location_name: z.string().nullable().default(null).describe(`full name of the location
21 |         optional field
22 |         in format "Country"
23 |         example:
24 |         United Kingdom`),
25 |       keywords: z.array(z.string()).describe(`keywords
26 |         the maximum number of keywords you can specify: 5`),
27 |       type: z.enum(['web', 'news', 'ecommerce']).default('web').describe(`dataforseo trends type`),
28 |       date_from: z.string().optional().describe(`starting date of the time range
29 |           if you don’t specify this field, the current day and month of the preceding year will be used by default
30 |           minimal value for the web type: 2004-01-01
31 |           minimal value for other types: 2008-01-01
32 |           date format: "yyyy-mm-dd"
33 |           example:
34 |           "2019-01-15"`),
35 |       date_to: z.string().optional()
36 |           .describe(
37 |             `ending date of the time range
38 |             if you don’t specify this field, the today’s date will be used by default
39 |             date format: "yyyy-mm-dd"
40 |             example:
41 |             "2019-01-15"`),
42 |       time_range: z.enum(['past_4_hours', 'past_day', 'past_7_days', 'past_30_days', 'past_90_days', 'past_12_months', 'past_5_years'])
43 |           .default('past_7_days')
44 |           .describe(
45 |             `preset time ranges
46 |             if you specify date_from or date_to parameters, this field will be ignored when setting a task`),
47 |     };
48 |   }
49 | 
50 |   async handle(params: any): Promise<any> {
51 |     try {
52 |       const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/dataforseo_trends/explore/live', 'POST', [{
53 |         location_name: params.location_name,
54 |         keywords: params.keywords,
55 |         type: params.type,
56 |         date_from: params.date_from,
57 |         date_to: params.date_to,
58 |         time_range: params.time_range,
59 |       }]);
60 |       return this.validateAndFormatResponse(response);
61 |     } catch (error) {
62 |       return this.formatErrorResponse(error);
63 |     }
64 |   }
65 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/tools/serp-organic-live-advanced.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { BaseTool } from '../../base.tool.js';
 3 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 4 | 
 5 | export class SerpOrganicLiveAdvancedTool extends BaseTool {
 6 |   constructor(dataForSEOClient: DataForSEOClient) {
 7 |     super(dataForSEOClient);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'serp_organic_live_advanced';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return 'Get organic search results for a keyword in specified search engine';
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       search_engine: z.string().default('google').describe("search engine name, one of: google, yahoo, bing."),
21 |       location_name: z.string().default('United States').describe(`full name of the location
22 | required field
23 | Location format - hierarchical, comma-separated (from most specific to least)
24 |  Can be one of:
25 |  1. Country only: "United States"
26 |  2. Region,Country: "California,United States"
27 |  3. City,Region,Country: "San Francisco,California,United States"`),
28 |       depth: z.number().min(10).max(700).default(10).describe(`parsing depth
29 | optional field
30 | number of results in SERP`),
31 |       language_code: z.string().describe("search engine language code (e.g., 'en')"),
32 |       keyword: z.string().describe("Search keyword"),
33 |       max_crawl_pages: z.number().min(1).max(7).optional().default(1).describe(`page crawl limit
34 | optional field
35 | number of search results pages to crawl
36 | max value: 100
37 | Note: the max_crawl_pages and depth parameters complement each other`),
38 |       device: z.string().default('desktop').optional().describe(`device type
39 | optional field
40 | can take the values:desktop, mobile
41 | default value: desktop`),
42 |       people_also_ask_click_depth: z.number().min(1).max(4).optional()
43 |       .describe(`clicks on the corresponding element
44 |         specify the click depth on the people_also_ask element to get additional people_also_ask_element items;`)
45 |     };
46 |   }
47 | 
48 |   async handle(params:any): Promise<any> {
49 |     try {
50 |       console.error(JSON.stringify(params, null, 2));
51 |       const response = await this.dataForSEOClient.makeRequest(`/v3/serp/${params.search_engine}/organic/live/advanced`, 'POST', [{
52 |         location_name: params.location_name,
53 |         language_code: params.language_code,
54 |         keyword: params.keyword,
55 |         depth: params.depth,
56 |         max_crawl_pages: params.max_crawl_pages,
57 |         device: params.device,
58 |         people_also_ask_click_depth: params.people_also_ask_click_depth && params.people_also_ask_click_depth > 0 ? params.people_also_ask_click_depth : undefined,
59 |       }]);
60 |       return this.validateAndFormatResponse(response);
61 |     } catch (error) {
62 |       return this.formatErrorResponse(error);
63 |     }
64 |   }
65 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/domain-analytics/tools/whois/whois-overview.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../../base.tool.js';
 4 | 
 5 | export class WhoisOverviewTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'domain_analytics_whois_overview';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with Whois data enriched with backlink stats, and ranking and traffic info from organic and paid search results. Using this endpoint you will be able to get all these data for the domains matching the parameters you specify in the request`;
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned domains"),
21 |       offset: z.number().min(0).optional().describe(
22 |         `offset in the results array of returned businesses
23 | optional field
24 | default value: 0
25 | if you specify the 10 value, the first ten entities in the results array will be omitted and the data will be provided for the successive entities`
26 |       ),
27 |       filters: this.getFilterExpression().optional().describe(
28 |         `array of results filtering parameters
29 | optional field
30 | you can add several filters at once (8 filters maximum)
31 | you should set a logical operator and, or between the conditions
32 | the following operators are supported:
33 | regex, not_regex, <, <=, >, >=, =, <>, in, not_in, like, not_like, match, not_match
34 | you can use the % operator with like and not_like to match any string of zero or more characters
35 | example:
36 | ["rating.value",">",3]`
37 |       ),
38 |       order_by: z.array(z.string()).optional().describe(
39 |         `results sorting rules
40 | optional field
41 | you can use the same values as in the filters array to sort the results
42 | possible sorting types:
43 | asc – results will be sorted in the ascending order
44 | desc – results will be sorted in the descending order
45 | you should use a comma to set up a sorting parameter
46 | example:
47 | ["rating.value,desc"]note that you can set no more than three sorting rules in a single request
48 | you should use a comma to separate several sorting rules
49 | example:
50 | ["rating.value,desc","rating.votes_count,desc"]`
51 |       ),
52 |       is_claimed: z.boolean().optional().describe(`indicates whether the business is verified by its owner on Google Maps`).default(true)
53 |     };
54 |   }
55 | 
56 |   async handle(params: any): Promise<any> {
57 |     try {
58 |       const response = await this.client.makeRequest('/v3/domain_analytics/whois/overview/live', 'POST', [{        
59 |         limit: params.limit,
60 |         offset: params.offset,
61 |         filters: this.formatFilters(params.filters),
62 |         order_by: this.formatOrderBy(params.order_by),
63 |       }]);
64 |       return this.validateAndFormatResponse(response);
65 |     } catch (error) {
66 |       return this.formatErrorResponse(error);
67 |     }
68 |   }
69 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-timeseries-summary.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksTimeseriesSummaryTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_timeseries_summary';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with an overview of backlink data for the target domain available during a period between the two indicated dates. Backlink metrics will be grouped by the time range that you define: day, week, month, or year.
16 | Data from this endpoint will be especially helpful for building time-series graphs of daily, weekly, monthly, and yearly link-building progress`;
17 |   }
18 | 
19 |   getParams(): z.ZodRawShape {
20 |     return {
21 |       target: z.string().describe(`domain to get data for
22 | required field
23 | a domain should be specified without https:// and www.
24 | example:
25 | "forbes.com"`),
26 |       date_from: z.string().describe(`starting date of the time range
27 | optional field
28 | this field indicates the date which will be used as a threshold for summary data;
29 | minimum value: 2019-01-30
30 | maximum value shouldn’t exceed the date specified in the date_to
31 | date format: "yyyy-mm-dd"
32 | example:
33 | "2021-01-01"`).optional(),
34 |       date_to: z.string().describe(`ending date of the time range
35 | optional field
36 | if you don’t specify this field, the today’s date will be used by default
37 | minimum value shouldn’t preceed the date specified in the date_from
38 | maximum value: today’s date
39 | date format: "yyyy-mm-dd"
40 | example:
41 | "2021-01-15"`).optional(),
42 |         group_range: z.string().optional().describe(`time range which will be used to group the results
43 | optional field
44 | default value: month
45 | possible values: day, week, month, year
46 | note: for day, we will return items corresponding to all dates between and including date_from and date_to;
47 | for week/month/year, we will return items corresponding to full weeks/months/years, where each item will indicate the last day of the week/month/year
48 | 
49 | for example, if you specify:
50 | "group_range": "month",
51 | "date_from": "2022-03-23",
52 | "date_to": "2022-05-13"
53 | we will return items falling between 2022-03-01 and 2022-05-31, namely, three items corresponding to the following dates: 2022-03-31, 2022-04-30, 2022-05-31
54 | 
55 | if there is no data for a certain day/week/month/year, we will return 0`).default('month')
56 |     };
57 |   }
58 | 
59 |   async handle(params: any): Promise<any> {
60 |     try {
61 |       const response = await this.client.makeRequest('/v3/backlinks/timeseries_summary/live', 'POST', [{
62 |         target: params.target,
63 |         date_from: params.date_from,
64 |         date_to: params.date_to,
65 |         group_range: params.group_range
66 |       }]);
67 |       return this.validateAndFormatResponse(response);
68 |     } catch (error) {
69 |       return this.formatErrorResponse(error);
70 |     }
71 |   }
72 | } 
```

--------------------------------------------------------------------------------
/src/core/config/field-configuration.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import * as fs from 'fs';
 2 | 
 3 | export interface FieldConfiguration {
 4 |   supported_fields: Record<string, string[]>;
 5 | }
 6 | 
 7 | export class FieldConfigurationManager {
 8 |   private static instance: FieldConfigurationManager;
 9 |   private config: FieldConfiguration | null = null;
10 | 
11 |   private constructor() {}
12 | 
13 |   public static getInstance(): FieldConfigurationManager {
14 |     if (!FieldConfigurationManager.instance) {
15 |       FieldConfigurationManager.instance = new FieldConfigurationManager();
16 |     }
17 |     return FieldConfigurationManager.instance;
18 |   }
19 | 
20 |   public loadFromFile(configPath: string): void {
21 |     try {
22 |       if (!fs.existsSync(configPath)) {
23 |         console.warn(`Configuration file not found: ${configPath}`);
24 |         return;
25 |       }
26 |       console.error(`Loading field configuration from: ${configPath}`);
27 |       const configContent = fs.readFileSync(configPath, 'utf8');
28 |       const parsedConfig = JSON.parse(configContent);
29 | 
30 |       // Validate the configuration structure
31 |       if (!parsedConfig.supported_fields || typeof parsedConfig.supported_fields !== 'object') {
32 |         throw new Error('Invalid configuration format. Expected { "supported_fields": { "tool_name": ["field1", "field2"] } }');
33 |       }
34 | 
35 |       this.config = parsedConfig;
36 |       console.log(`Field configuration loaded from: ${configPath}`);
37 |     } catch (error) {
38 |       console.error('Error loading field configuration:', error);
39 |       throw error;
40 |     }
41 |   }
42 | 
43 |   public getFieldsForTool(toolName: string): string[] | null {
44 |     if (!this.config) {
45 |       return null; // No configuration loaded, return all fields
46 |     }
47 | 
48 |     return this.config.supported_fields[toolName] || null;
49 |   }
50 | 
51 |   public hasConfiguration(): boolean {
52 |     return this.config !== null;
53 |   }
54 | 
55 |   public isToolConfigured(toolName: string): boolean {
56 |     return this.config !== null && toolName in this.config.supported_fields;
57 |   }
58 | 
59 |   public getConfiguration(): FieldConfiguration | null {
60 |     return this.config;
61 |   }
62 | 
63 |   public clearConfiguration(): void {
64 |     this.config = null;
65 |   }
66 | }
67 | 
68 | // Helper functions for easy access
69 | export function getFieldsForTool(toolName: string): string[] | null {
70 |   return FieldConfigurationManager.getInstance().getFieldsForTool(toolName);
71 | }
72 | 
73 | export function isToolConfigured(toolName: string): boolean {
74 |   return FieldConfigurationManager.getInstance().isToolConfigured(toolName);
75 | }
76 | 
77 | export function hasFieldConfiguration(): boolean {
78 |   return FieldConfigurationManager.getInstance().hasConfiguration();
79 | }
80 | 
81 | export function loadFieldConfiguration(configPath: string): void {
82 |   FieldConfigurationManager.getInstance().loadFromFile(configPath);
83 | }
84 | 
85 | export function initializeFieldConfiguration(): void {
86 |   const configPath = process.env.FIELD_CONFIG_PATH;
87 |   
88 |   if (configPath) {
89 |     try {
90 |       loadFieldConfiguration(configPath);
91 |     } catch (error) {
92 |       console.error('Failed to load field configuration:', error);
93 |     }
94 |   }
95 | }
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-timeseries-new-lost-summary.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksTimeseriesNewLostSummaryTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_timeseries_new_lost_summary';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return `This endpoint will provide you with the number of new and lost backlinks and referring domains for the domain specified in the target field.
16 | The results will be provided for a period between the two indicated dates, and metrics will be grouped by the time range that you define: day, week, month, or year.
17 | Data from this endpoint will be especially helpful for building time-series graphs of new and lost backlinks and referring domains.
18 | 
19 | `;
20 |   }
21 | 
22 |   getParams(): z.ZodRawShape {
23 |     return {
24 |       target: z.string().describe(`domain to get data for
25 | required field
26 | a domain should be specified without https:// and www.
27 | example:
28 | "forbes.com"`),
29 |       date_from: z.string().describe(`starting date of the time range
30 | optional field
31 | this field indicates the date which will be used as a threshold for summary data;
32 | minimum value: 2019-01-30
33 | maximum value shouldn’t exceed the date specified in the date_to
34 | date format: "yyyy-mm-dd"
35 | example:
36 | "2021-01-01"`).optional(),
37 |       date_to: z.string().describe(`ending date of the time range
38 | optional field
39 | if you don’t specify this field, the today’s date will be used by default
40 | minimum value shouldn’t preceed the date specified in the date_from
41 | maximum value: today’s date
42 | date format: "yyyy-mm-dd"
43 | example:
44 | "2021-01-15"`).optional(),
45 |         group_range: z.string().optional().describe(`time range which will be used to group the results
46 | optional field
47 | default value: month
48 | possible values: day, week, month, year
49 | note: for day, we will return items corresponding to all dates between and including date_from and date_to;
50 | for week/month/year, we will return items corresponding to full weeks/months/years, where each item will indicate the last day of the week/month/year
51 | 
52 | for example, if you specify:
53 | "group_range": "month",
54 | "date_from": "2022-03-23",
55 | "date_to": "2022-05-13"
56 | we will return items falling between 2022-03-01 and 2022-05-31, namely, three items corresponding to the following dates: 2022-03-31, 2022-04-30, 2022-05-31
57 | 
58 | if there is no data for a certain day/week/month/year, we will return 0`).default('month')
59 |     };
60 |   }
61 | 
62 |   async handle(params: any): Promise<any> {
63 |     try {
64 |       const response = await this.client.makeRequest('/v3/backlinks/timeseries_new_lost_summary/live', 'POST', [{
65 |         target: params.target,
66 |         date_from: params.date_from,
67 |         date_to: params.date_to,
68 |         group_range: params.group_range
69 |       }]);
70 |       return this.validateAndFormatResponse(response);
71 |     } catch (error) {
72 |       return this.formatErrorResponse(error);
73 |     }
74 |   }
75 | } 
```

--------------------------------------------------------------------------------
/src/core/utils/field-filter.ts:
--------------------------------------------------------------------------------

```typescript
  1 | type FieldPath = string | string[];
  2 | 
  3 | export function filterFields(data: any, fields: FieldPath[]): any {
  4 |   if (!data || !fields || fields.length === 0) {
  5 |     return data;
  6 |   }
  7 | 
  8 |   const result: any = {};
  9 | 
 10 |   fields.forEach(field => {
 11 |     const path = Array.isArray(field) ? field : field.split('.');
 12 |     extractAndSetValue(data, result, path);
 13 |   });
 14 | 
 15 |   return result;
 16 | }
 17 | 
 18 | function extractAndSetValue(source: any, target: any, path: string[]): void {
 19 |   if (path.length === 0) return;
 20 | 
 21 |   const [currentKey, ...remainingPath] = path;
 22 |   
 23 |   if (remainingPath.length === 0) {
 24 |     // This is the final key, extract the value
 25 |     if (currentKey === '*') {
 26 |       // Wildcard at the end - copy all properties/items
 27 |       if (Array.isArray(source)) {
 28 |         Object.assign(target, source);
 29 |       } else if (source && typeof source === 'object') {
 30 |         Object.assign(target, source);
 31 |       }
 32 |     } else if (source && typeof source === 'object' && currentKey in source) {
 33 |       target[currentKey] = source[currentKey];
 34 |     }
 35 |     return;
 36 |   }
 37 | 
 38 |   // Not the final key, need to go deeper
 39 |   if (currentKey === '*') {
 40 |     // Wildcard in the middle of the path
 41 |     if (Array.isArray(source)) {
 42 |       // Handle array with wildcard
 43 |       if (!Array.isArray(target)) {
 44 |         // Convert target to array to match source structure
 45 |         Object.keys(target).forEach(key => delete target[key]);
 46 |         Object.setPrototypeOf(target, Array.prototype);
 47 |         target.length = 0;
 48 |       }
 49 |       
 50 |       source.forEach((item, index) => {
 51 |         if (!target[index]) {
 52 |           target[index] = {};
 53 |         }
 54 |         extractAndSetValue(item, target[index], remainingPath);
 55 |       });
 56 |     } else if (source && typeof source === 'object') {
 57 |       // Handle object with wildcard
 58 |       Object.keys(source).forEach(key => {
 59 |         if (!target[key]) {
 60 |           target[key] = {};
 61 |         }
 62 |         extractAndSetValue(source[key], target[key], remainingPath);
 63 |       });
 64 |     }
 65 |   } else if (source && typeof source === 'object' && currentKey in source) {
 66 |     // Regular key handling
 67 |     const sourceValue = source[currentKey];
 68 |     
 69 |     if (Array.isArray(sourceValue)) {
 70 |       // Handle array - preserve array structure
 71 |       if (!target[currentKey]) {
 72 |         target[currentKey] = [];
 73 |       }
 74 |       
 75 |       sourceValue.forEach((item, index) => {
 76 |         if (!target[currentKey][index]) {
 77 |           target[currentKey][index] = {};
 78 |         }
 79 |         extractAndSetValue(item, target[currentKey][index], remainingPath);
 80 |       });
 81 |     } else if (sourceValue && typeof sourceValue === 'object') {
 82 |       // Handle object
 83 |       if (!target[currentKey]) {
 84 |         target[currentKey] = {};
 85 |       }
 86 |       extractAndSetValue(sourceValue, target[currentKey], remainingPath);
 87 |     }
 88 |   }
 89 | }
 90 | 
 91 | export function parseFieldPaths(fields: string[]): FieldPath[] {
 92 |   return fields.map(field => {
 93 |     // Handle array notation
 94 |     if (field.includes('[')) {
 95 |       const [base, index] = field.split('[');
 96 |       return [base, index.replace(']', '')];
 97 |     }
 98 |     return field;
 99 |   });
100 | }
101 | 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-anchor.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksAnchorTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_anchors';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "This endpoint will provide you with a detailed overview of anchors used when linking to the specified website with relevant backlink data for each of them";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`domain, subdomain or webpage to get backlinks for
21 |         required field
22 | a domain or a subdomain should be specified without https:// and www.
23 | a page should be specified with absolute URL (including http:// or https://)`),
24 |       limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned anchors"),
25 |       offset: z.number().min(0).optional().describe(
26 |         `offset in the results array of returned anchors
27 | optional field
28 | default value: 0
29 | if you specify the 10 value, the first ten anchors in the results array will be omitted and the data will be provided for the successive anchors`
30 |       ),
31 |       filters: this.getFilterExpression().optional().describe(
32 |         `array of results filtering parameters
33 | optional field
34 | you can add several filters at once (8 filters maximum)
35 | you should set a logical operator and, or between the conditions
36 | the following operators are supported:
37 | =, <>, in, not_in, like, not_like, ilike, not_ilike, regex, not_regex, match, not_match
38 | you can use the % operator with like and not_like to match any string of zero or more characters
39 | example:
40 | ["rank",">","80"]
41 | [["page_from_rank",">","55"],
42 | "and",
43 | ["dofollow","=",true]]
44 | 
45 | [["first_seen",">","2017-10-23 11:31:45 +00:00"],
46 | "and",
47 | [["anchor","like","%seo%"],"or",["text_pre","like","%seo%"]]]`
48 |       ),
49 |       order_by: z.array(z.string()).optional().describe(
50 |         `results sorting rules
51 | optional field
52 | you can use the same values as in the filters array to sort the results
53 | possible sorting types:
54 | asc – results will be sorted in the ascending order
55 | desc – results will be sorted in the descending order
56 | you should use a comma to set up a sorting type
57 | example:
58 | ["rank,desc"]
59 | note that you can set no more than three sorting rules in a single request
60 | you should use a comma to separate several sorting rules
61 | example:
62 | ["domain_from_rank,desc","page_from_rank,asc"]`
63 |       ),
64 |     };
65 |   }
66 | 
67 |   async handle(params: any): Promise<any> {
68 |     try {
69 |       const response = await this.client.makeRequest('/v3/backlinks/anchors/live', 'POST', [{
70 |         target: params.target,
71 |         limit: params.limit,
72 |         offset: params.offset,
73 |         filters: this.formatFilters(params.filters),
74 |         order_by: this.formatOrderBy(params.order_by)
75 |       }]);
76 |       return this.validateAndFormatResponse(response);
77 |     } catch (error) {
78 |       return this.formatErrorResponse(error);
79 |     }
80 |   }
81 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-domain-pages.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksDomainPagesTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_domain_pages';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "This endpoint will provide you with a detailed overview of domain pages with backlink data for each page";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`domain, subdomain or webpage to get backlinks for
21 |         required field
22 | a domain or a subdomain should be specified without https:// and www.
23 | a page should be specified with absolute URL (including http:// or https://)`),
24 |       limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned pages"),
25 |       offset: z.number().min(0).optional().describe(
26 |         `offset in the results array of returned pages
27 | optional field
28 | default value: 0
29 | if you specify the 10 value, the first ten pages in the results array will be omitted and the data will be provided for the successive pages`
30 |       ),
31 |       filters: this.getFilterExpression().optional().describe(
32 |         `array of results filtering parameters
33 | optional field
34 | you can add several filters at once (8 filters maximum)
35 | you should set a logical operator and, or between the conditions
36 | the following operators are supported:
37 | regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
38 | you can use the % operator with like and not_like to match any string of zero or more characters
39 | example:
40 | ["meta.internal_links_count",">","1"]
41 | [["meta.external_links_count",">","2"],
42 | "and",
43 | ["backlinks",">","10"]]
44 | 
45 | [["first_visited",">","2017-10-23 11:31:45 +00:00"],
46 | "and",
47 | [["title","like","%seo%"],"or",["referring_domains",">","10"]]]`
48 |       ),
49 |       order_by: z.array(z.string()).optional().describe(
50 |         `results sorting rules
51 | optional field
52 | you can use the same values as in the filters array to sort the results
53 | possible sorting types:
54 | asc – results will be sorted in the ascending order
55 | desc – results will be sorted in the descending order
56 | you should use a comma to set up a sorting type
57 | example:
58 | ["page_summary.backlinks,desc"]
59 | note that you can set no more than three sorting rules in a single request
60 | you should use a comma to separate several sorting rules
61 | example:
62 | ["page_summary.backlinks,desc","page_summary.rank,asc"]`
63 |       ),
64 |     };
65 |   }
66 | 
67 |   async handle(params: any): Promise<any> {
68 |     try {
69 |       const response = await this.client.makeRequest('/v3/backlinks/domain_pages/live', 'POST', [{
70 |         target: params.target,
71 |         limit: params.limit,
72 |         offset: params.offset,
73 |         filters: this.formatFilters(params.filters),
74 |         order_by: this.formatOrderBy(params.order_by),
75 |       }]);
76 |       return this.validateAndFormatResponse(response);
77 |     } catch (error) {
78 |       return this.formatErrorResponse(error);
79 |     }
80 |   }
81 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-referring-domains.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksReferringDomainsTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);    
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_referring_domains';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "This endpoint will provide you with a detailed overview of referring domains pointing to the target you specify";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`domain, subdomain or webpage to get backlinks for
21 |         required field
22 | a domain or a subdomain should be specified without https:// and www.
23 | a page should be specified with absolute URL (including http:// or https://)`),
24 |       limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned pages"),
25 |       offset: z.number().min(0).optional().describe(
26 |         `offset in the results array of returned pages
27 | optional field
28 | default value: 0
29 | if you specify the 10 value, the first ten pages in the results array will be omitted and the data will be provided for the successive pages`
30 |       ),
31 |       filters: this.getFilterExpression().optional().describe(
32 |         `array of results filtering parameters
33 | optional field
34 | you can add several filters at once (8 filters maximum)
35 | you should set a logical operator and, or between the conditions
36 | the following operators are supported:
37 | regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
38 | you can use the % operator with like and not_like to match any string of zero or more characters
39 | example:
40 | ["meta.internal_links_count",">","1"]
41 | [["meta.external_links_count",">","2"],
42 | "and",
43 | ["backlinks",">","10"]]
44 | 
45 | [["first_visited",">","2017-10-23 11:31:45 +00:00"],
46 | "and",
47 | [["title","like","%seo%"],"or",["referring_domains",">","10"]]]`
48 |       ),
49 |       order_by: z.array(z.string()).optional().describe(
50 |         `results sorting rules
51 | optional field
52 | you can use the same values as in the filters array to sort the results
53 | possible sorting types:
54 | asc – results will be sorted in the ascending order
55 | desc – results will be sorted in the descending order
56 | you should use a comma to set up a sorting type
57 | example:
58 | ["page_summary.backlinks,desc"]
59 | note that you can set no more than three sorting rules in a single request
60 | you should use a comma to separate several sorting rules
61 | example:
62 | ["page_summary.backlinks,desc","page_summary.rank,asc"]`
63 |       ),
64 |     };
65 |   }
66 | 
67 |   async handle(params: any): Promise<any> {
68 |     try {
69 |       const response = await this.client.makeRequest('/v3/backlinks/referring_domains/live', 'POST', [{
70 |         target: params.target,
71 |         limit: params.limit,
72 |         offset: params.offset,
73 |         filters: this.formatFilters(params.filters),
74 |         order_by: this.formatOrderBy(params.order_by),
75 |       }]);
76 |       return this.validateAndFormatResponse(response);
77 |     } catch (error) {
78 |       return this.formatErrorResponse(error);
79 |     }
80 |   }
81 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/business-data-api/tools/listings/business-listings-filters.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
 3 | import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';
 4 | 
 5 | interface FilterField {
 6 |   type: string;
 7 |   path: string;
 8 | }
 9 | 
10 | interface ToolFilters {
11 |   [key: string]: {
12 |     [field: string]: string;
13 |   };
14 | }
15 | 
16 | export class BusinessListingsFiltersTool extends BaseTool {
17 |   private static cache: ToolFilters | null = null;
18 |   private static lastFetchTime: number = 0;
19 |   private static readonly CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
20 | 
21 |   // Map of tool names to their corresponding filter paths in the API response
22 |   private static readonly TOOL_TO_FILTER_MAP: { [key: string]: string } = {
23 |     'business_data_business_listings_search': 'search',
24 |     'business_data_business_listings_categories_aggregation': 'categories_aggregation'
25 |   };
26 | 
27 |   constructor(private client: DataForSEOClient) {
28 |     super(client);
29 |   }
30 | 
31 |   getName(): string {
32 |     return 'business_data_business_listings_filters';
33 |   }
34 | 
35 |   getDescription(): string {
36 |     return `Here you will find all the necessary information about filters that can be used with Business Data API business listings endpoints.
37 | 
38 | Please, keep in mind that filters are associated with a certain object in the result array, and should be specified accordingly.`;
39 |   }
40 | 
41 |   protected supportOnlyFullResponse(): boolean {
42 |     return true;
43 |   }
44 | 
45 |   getParams(): z.ZodRawShape {
46 |     return {
47 |       tool: z.string().optional().describe('The name of the tool to get filters for')
48 |     };
49 |   }
50 | 
51 |   private async fetchAndCacheFilters(): Promise<ToolFilters> {
52 |     const now = Date.now();
53 |     
54 |     // Return cached data if it's still valid
55 |     if (BusinessListingsFiltersTool.cache && 
56 |         (now - BusinessListingsFiltersTool.lastFetchTime) < BusinessListingsFiltersTool.CACHE_TTL) {
57 |       return BusinessListingsFiltersTool.cache;
58 |     }
59 | 
60 |     // Fetch fresh data
61 |     const response = await this.client.makeRequest('/v3/business_data/business_listings/available_filters', 'GET', null, true) as DataForSEOFullResponse;
62 |     this.validateResponseFull(response);
63 | 
64 |     // Transform the response into our cache format
65 |     const filters: ToolFilters = {};
66 |     const result = response.tasks[0].result[0];
67 | 
68 |     // Process each tool's filters
69 |     for (const [toolName, filterPath] of Object.entries(BusinessListingsFiltersTool.TOOL_TO_FILTER_MAP)) {
70 |       if (result && result[filterPath]) {
71 |         filters[toolName] = result[filterPath];
72 |       }
73 |     }
74 | 
75 |     // Update cache
76 |     BusinessListingsFiltersTool.cache = filters;
77 |     BusinessListingsFiltersTool.lastFetchTime = now;
78 |     return filters;
79 |   }
80 | 
81 |   async handle(params: any): Promise<any> {
82 |     try {
83 |       const filters = await this.fetchAndCacheFilters();
84 |       
85 |       if (!params.tool) {
86 |         return this.formatResponse(filters);
87 |       }
88 | 
89 |       const toolFilters = filters[params.tool];
90 |       if (!toolFilters) {
91 |         throw new Error(`No filters found for tool: ${params.tool}`);
92 |       }
93 | 
94 |       return this.formatResponse(toolFilters);
95 |     } catch (error) {
96 |       return this.formatErrorResponse(error);
97 |     }
98 |   }
99 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/serp/serp.prompt.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { PromptDefinition } from '../prompt-definition.js';
 3 | 
 4 | 
 5 | export const serpPrompts: PromptDefinition[] = [
 6 |   {
 7 |     name: 'analyze_local_seo_differences_in_the_top_10_google_results_for_two_target_markets',
 8 |     title: 'Analyze local SEO differences in the top 10 Google results for two target markets.',
 9 |     params: {
10 |       keyword: z.string().describe('The keyword to analyze'),
11 |       language: z.string().describe('The language of the search results'),
12 |       location1: z.string().describe('The first location to analyze'),
13 |       location2: z.string().describe('The second location to analyze'),
14 |     },
15 |     handler: async (params) => {
16 |       return {
17 |         messages: [
18 |           {
19 |             role: 'user',
20 |             content: {
21 |               type: 'text',
22 |               text: `Use the serp_organic_live_advanced API to fetch the top 10 results and SERP features for the keyword '${params.keyword}' in '${params.language}' for two locations with two separate API requests: '${params.location1}', then in '${params.location2}'. Display a unified table of the top 10 results for both locations side-by-side, with columns: Rank, Domain, Title, Snippet (shorten), URL, and Element Type (e.g., Organic, Knowledge Graph, Featured Snippet, etc.).`
23 |             }
24 |           }
25 |         ]
26 |       };
27 |     }
28 |   },
29 |   {
30 |     name: 'monitor_visibility_for_key_branded_searches_in_real_time',
31 |     title: 'Monitor visibility for key branded searches in real-time.',
32 |     params: {
33 |       domain: z.string().describe('The domain to monitor'),
34 |       location: z.string().describe('The location to monitor'),
35 |       language: z.string().describe('The language to monitor'),
36 |     },
37 |     handler: async (params) => {
38 |       return {
39 |         messages: [
40 |           {
41 |             role: 'user',
42 |             content: {
43 |               type: 'text',
44 |               text: `Using the real-time SERP API, check if '${params.domain}' currently ranks in the top 3 organic results or SERP features like featured snippet or knowledge graph for the branded keyword [keyword] in '${params.location}' in '${params.language}'. Indicate what competitors are showing in SERP features too if my domain is not featured.`
45 |             }
46 |           }
47 |         ]
48 |       };
49 |     }
50 |   },
51 |   {
52 |     name: 'generate_domain_visibility_reports_and_track_ranking_changes',
53 |     title: 'Generate domain visibility reports and track ranking changes.',
54 |     params: {
55 |       domain: z.string().describe('The domain to analyze'),
56 |       location: z.string().describe('The location to analyze'),
57 |       language: z.string().describe('The language to analyze'),
58 |     },
59 |     handler: async (params) => {
60 |       return {
61 |         messages: [
62 |           {
63 |             role: 'user',
64 |             content: {
65 |               type: 'text',
66 |               text: `Generate a domain visibility snapshot for '${params.domain}' in '${params.location}' in '${params.language}' using google_domain_rank_overview. List the estimated organic traffic, percentage of top 10 rankings, and SERP position breakdown for this week. Compare to last month's values using google_historical_rank_overview.`
67 |             }
68 |           }
69 |         ]
70 |       };
71 |     }
72 |   },
73 | ]   
74 | 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-domain-pages-summary.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { DataForSEOClient } from '../../../client/dataforseo.client.js';
 3 | import { BaseTool } from '../../base.tool.js';
 4 | 
 5 | export class BacklinksDomainPagesSummaryTool extends BaseTool {
 6 |   constructor(private client: DataForSEOClient) {
 7 |     super(client);    
 8 |   }
 9 | 
10 |   getName(): string {
11 |     return 'backlinks_domain_pages_summary';
12 |   }
13 | 
14 |   getDescription(): string {
15 |     return "This endpoint will provide you with detailed summary data on all backlinks and related metrics for each page of the target domain or subdomain you specify. If you indicate a single page as a target, you will get comprehensive summary data on all backlinks for that page";
16 |   }
17 | 
18 |   getParams(): z.ZodRawShape {
19 |     return {
20 |       target: z.string().describe(`domain, subdomain or webpage to get backlinks for
21 |         required field
22 | a domain or a subdomain should be specified without https:// and www.
23 | a page should be specified with absolute URL (including http:// or https://)`),
24 |       limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned anchors"),
25 |       offset: z.number().min(0).optional().describe(
26 |         `offset in the results array of returned anchors
27 | optional field
28 | default value: 0
29 | if you specify the 10 value, the first ten anchors in the results array will be omitted and the data will be provided for the successive anchors`
30 |       ),
31 |       filters: this.getFilterExpression().optional().describe(
32 |         `array of results filtering parameters
33 | optional field
34 | you can add several filters at once (8 filters maximum)
35 | you should set a logical operator and, or between the conditions
36 | the following operators are supported:
37 | regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
38 | you can use the % operator with like and not_like to match any string of zero or more characters
39 | example:
40 | ["referring_links_types.anchors",">","1"]
41 | [["broken_pages",">","2"],
42 | "and",
43 | ["backlinks",">","10"]]
44 | 
45 | [["first_seen",">","2017-10-23 11:31:45 +00:00"],
46 | "and",
47 | [["anchor","like","%seo%"],"or",["referring_domains",">","10"]]]`
48 |       ),
49 |       order_by: z.array(z.string()).optional().describe(
50 |         `results sorting rules
51 | optional field
52 | you can use the same values as in the filters array to sort the results
53 | possible sorting types:
54 | asc – results will be sorted in the ascending order
55 | desc – results will be sorted in the descending order
56 | you should use a comma to set up a sorting type
57 | example:
58 | ["backlinks,desc"]
59 | note that you can set no more than three sorting rules in a single request
60 | you should use a comma to separate several sorting rules
61 | example:
62 | ["backlinks,desc","rank,asc"]`
63 |       ),
64 |     };
65 |   }
66 | 
67 |   async handle(params: any): Promise<any> {
68 |     try {
69 |       const response = await this.client.makeRequest('/v3/backlinks/domain_pages_summary/live', 'POST', [{
70 |         target: params.target,
71 |         limit: params.limit,
72 |         offset: params.offset,
73 |         filters: this.formatFilters(params.filters),
74 |         order_by: this.formatOrderBy(params.order_by),
75 |       }]);
76 |       return this.validateAndFormatResponse(response);
77 |     } catch (error) {
78 |       return this.formatErrorResponse(error);
79 |     }
80 |   }
81 | } 
```

--------------------------------------------------------------------------------
/src/core/modules/domain-analytics/tools/whois/whois-filters.tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
  3 | import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';
  4 | 
  5 | interface FilterField {
  6 |   type: string;
  7 |   path: string;
  8 | }
  9 | 
 10 | interface ToolFilters {
 11 |   [key: string]: {
 12 |     [field: string]: string;
 13 |   };
 14 | }
 15 | 
 16 | export class WhoisFiltersTool extends BaseTool {
 17 |   private static cache: ToolFilters | null = null;
 18 |   private static lastFetchTime: number = 0;
 19 |   private static readonly CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
 20 | 
 21 |   // Map of tool names to their corresponding filter paths in the API response
 22 |   private static readonly TOOL_TO_FILTER_MAP: { [key: string]: string } = {
 23 |     'domain_analytics_whois_overview': 'overview'
 24 |   };
 25 | 
 26 |   constructor(private client: DataForSEOClient) {
 27 |     super(client);
 28 |   }
 29 | 
 30 |   getName(): string {
 31 |     return 'domain_analytics_whois_available_filters';
 32 |   }
 33 | 
 34 |   getDescription(): string {
 35 |     return `Here you will find all the necessary information about filters that can be used with DataForSEO WHOIS API endpoints.
 36 | 
 37 | Please, keep in mind that filters are associated with a certain object in the result array, and should be specified accordingly.`;
 38 |   }
 39 | 
 40 |   protected supportOnlyFullResponse(): boolean {
 41 |     return true;
 42 |   }
 43 | 
 44 |   getParams(): z.ZodRawShape {
 45 |     return {
 46 |       tool: z.string().optional().describe('The name of the tool to get filters for')
 47 |     };
 48 |   }
 49 | 
 50 |   private async fetchAndCacheFilters(): Promise<ToolFilters> {
 51 |     const now = Date.now();
 52 |     
 53 |     // Return cached data if it's still valid
 54 |     if (WhoisFiltersTool.cache && 
 55 |         (now - WhoisFiltersTool.lastFetchTime) < WhoisFiltersTool.CACHE_TTL) {
 56 |       return WhoisFiltersTool.cache;
 57 |     }
 58 | 
 59 |     // Fetch fresh data
 60 |     const response = await this.client.makeRequest('/v3/domain_analytics/whois/available_filters', 'GET', null, true) as DataForSEOFullResponse;
 61 |     this.validateResponseFull(response);
 62 | 
 63 |     // Transform the response into our cache format
 64 |     const filters: ToolFilters = {};
 65 |     const result = response.tasks[0].result[0];
 66 | 
 67 |     // Process each tool's filters
 68 |     for (const [toolName, filterPath] of Object.entries(WhoisFiltersTool.TOOL_TO_FILTER_MAP)) {
 69 |       const pathParts = filterPath.split('.');
 70 |       let current = result;
 71 |       
 72 |       // Navigate to the correct filter object
 73 |       for (const part of pathParts) {
 74 |         if (current && current[part]) {
 75 |           current = current[part];
 76 |         } else {
 77 |           current = null;
 78 |           break;
 79 |         }
 80 |       }
 81 | 
 82 |       if (current) {
 83 |         filters[toolName] = current;
 84 |       }
 85 |     }
 86 | 
 87 |     // Update cache
 88 |     WhoisFiltersTool.cache = filters;
 89 |     WhoisFiltersTool.lastFetchTime = now;
 90 | 
 91 |     return filters;
 92 |   }
 93 | 
 94 |   async handle(params: any): Promise<any> {
 95 |     try {
 96 |       const filters = await this.fetchAndCacheFilters();
 97 |       
 98 |       if (!params.tool) {
 99 |         return this.formatResponse(filters);
100 |       }
101 | 
102 |       const toolFilters = filters[params.tool];
103 |       if (!toolFilters) {
104 |         throw new Error(`No filters found for tool: ${params.tool}`);
105 |       }
106 | 
107 |       return this.formatResponse(toolFilters);
108 |     } catch (error) {
109 |       return this.formatErrorResponse(error);
110 |     }
111 |   }
112 | } 
```
Page 1/4FirstPrevNextLast