#
tokens: 49402/50000 86/110 files (page 1/3)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 3. Use http://codebase.md/dataforseo/mcp-server-typescript?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:
--------------------------------------------------------------------------------

```
build/*
node_modules/*
.wrangler/*
src/worker/version.worker.ts
instruct.txt
.env
src/main/test2.ts
.vscode/launch.json
.vscode/tasks.json
package-lock.json


```

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

```
# Node modules
node_modules
npm-debug.log*

# Build output (will be generated during build)
build

# Git
.git
.gitignore

# Documentation
README.md
*.md

# IDE files
.vscode
.idea
*.swp
*.swo

# OS files
.DS_Store
Thumbs.db

# Environment files
.env
.env.local
.env.*.local

# Logs
logs
*.log

# Test files
test
tests
*.test.*
*.spec.*

# Coverage
coverage
.nyc_output

# Temporary files
.tmp
tmp

```

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

```markdown
# DataForSEO MCP Server

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. 

## Features

- **AI_OPTIMIZATION API**: provides data for keyword discovery, conversational optimization, and real-time LLM benchmarking; 
- **SERP API**: real-time Search Engine Results Page (SERP) data for Google, Bing, and Yahoo;
- **KEYWORDS_DATA API**: keyword research and clickstream data, including search volume, cost-per-click, and other metrics;   
- **ONPAGE API**: allows crawling websites and webpages according to customizable parameters to obtain on-page SEO performance metrics; 
- **DATAFORSEO LABS API**: data on keywords, SERPs, and domains based on DataForSEO's in-house databases and proprietary algorithms;
- **BACKLINKS API**: comprehensive backlink analysis including referring domains, anchor text distribution, and link quality metrics;
- **BUSINESS DATA API**: publicly available data on any business entity;
- **DOMAIN ANALYTICS API**: data on website traffic, technologies, and Whois details;
- **CONTENT ANALYSIS API**: robust source of data for brand monitoring, sentiment analysis, and citation management;

## Prerequisites

- Node.js (v14 or higher)
- DataForSEO API credentials (API login and password)

## Installation

1. Clone the repository:
```bash
git clone https://github.com/dataforseo/mcp-server-typescript
cd mcp-server-typescript
```

2. Install dependencies:
```bash
npm install
```

3. Set up environment variables:
```bash
# Required
export DATAFORSEO_USERNAME=your_username
export DATAFORSEO_PASSWORD=your_password

# Optional: specify which modules to enable (comma-separated)
# If not set, all modules will be enabled
export ENABLED_MODULES="SERP,KEYWORDS_DATA,ONPAGE,DATAFORSEO_LABS,BACKLINKS,BUSINESS_DATA,DOMAIN_ANALYTICS"

# Optional: specify which prompts in enabled modules are enable too (prompts names, comma-separated)
# If not set, all prompts from enabled modules will be enabled
export ENABLED_PROMPTS="top_3_google_result_domains,top_5_serp_paid_and_organic"

# Optional: enable full API responses
# If not set or set to false, the server will filter and transform API responses to a more concise format
# If set to true, the server will return the full, unmodified API responses
export DATAFORSEO_FULL_RESPONSE="false"

# Optional: enable simple filter schema
# If set to true, a simplified version of the filters schema will be used.
# This is required for ChatGPT APIs or other LLMs that cannot handle nested structures.
export DATAFORSEO_SIMPLE_FILTER="false"
```

## Installation as an NPM Package

You can install the package globally:

```bash
npm install -g dataforseo-mcp-server
```

Or run it directly without installation:

```bash
npx dataforseo-mcp-server
```

Remember to set environment variables before running the command:

```bash
# Required environment variables
export DATAFORSEO_USERNAME=your_username
export DATAFORSEO_PASSWORD=your_password

# Run with npx
npx dataforseo-mcp-server
```

## Building and Running

Build the project:
```bash
npm run build
```

Run the server:
```bash
# Start local server (direct MCP communication)
npx dataforseo-mcp-server

# Start HTTP server
npx dataforseo-mcp-server http
```

## HTTP Server Configuration

The server runs on port 3000 by default and supports both Basic Authentication and environment variable-based authentication.

To start the HTTP server, run:
```bash
npm run http
```

### Authentication Methods

1. **Basic Authentication**
   - Send requests with Basic Auth header:
   ```
   Authorization: Basic <base64-encoded-credentials>
   ```
   - Credentials format: `username:password`

2. **Environment Variables**
   - If no Basic Auth is provided, the server will use credentials from environment variables:
   ```bash
   export DATAFORSEO_USERNAME=your_username
   export DATAFORSEO_PASSWORD=your_password
   # Optional
   export DATAFORSEO_SIMPLE_FILTER="false"
   export DATAFORSEO_FULL_RESPONSE="true"
   ```

## Cloudflare Worker Deployment

The DataForSEO MCP Server can be deployed as a Cloudflare Worker for serverless, edge-distributed access to DataForSEO APIs.

### Worker Features

- **Edge Distribution**: Deploy globally across Cloudflare's edge network
- **Serverless**: No server management required
- **Auto-scaling**: Handles traffic spikes automatically
- **MCP Protocol Support**: Compatible with both Streamable HTTP and SSE transports
- **Environment Variables**: Secure credential management through Cloudflare dashboard

### Quick Start

1. **Install Wrangler CLI**:
   ```bash
   npm install -g wrangler
   ```

2. **Configure Worker**:
   ```bash
   # Login to Cloudflare
   wrangler login
   
   # Set environment variables
   wrangler secret put DATAFORSEO_USERNAME
   wrangler secret put DATAFORSEO_PASSWORD
   ```

3. **Deploy Worker**:
   ```bash
   # Build and deploy
   npm run build
   wrangler deploy --main build/index-worker.js
   ```

### Configuration

The worker uses the same environment variables as the standard server:

- `DATAFORSEO_USERNAME`: Your DataForSEO username
- `DATAFORSEO_PASSWORD`: Your DataForSEO password  
- `ENABLED_MODULES`: Comma-separated list of modules to enable
- `ENABLED_PROMPTS`: Comma-separated list of prompt names to enable 
- `DATAFORSEO_FULL_RESPONSE`: Set to "true" for full API responses

### Worker Endpoints

Once deployed, your worker will be available at `https://your-worker.your-subdomain.workers.dev/` with the following endpoints:

- **POST /mcp**: Streamable HTTP transport (recommended)
- **GET /sse**: SSE connection establishment (deprecated)
- **POST /messages**: SSE message handling (deprecated)
- **GET /health**: Health check endpoint
- **GET /**: API documentation page

### Advanced Configuration

Edit `wrangler.jsonc` to customize your deployment:

```jsonc
{
  "name": "dataforseo-mcp-worker",
  "main": "build/index-worker.js",
  "compatibility_date": "2025-07-10",
  "compatibility_flags": ["nodejs_compat"],
  "vars": {
    "ENABLED_MODULES": "SERP,KEYWORDS_DATA,ONPAGE,DATAFORSEO_LABS",
    "ENABLED_PROMPTS":"top_3_google_result_domains,top_5_serp_paid_and_organic"
  }
}
```

### Usage with Claude

After deployment, configure Claude to use your worker:

```json
{
  "name": "DataForSEO",
  "description": "Access DataForSEO APIs via Cloudflare Worker",
  "transport": {
    "type": "http",
    "baseUrl": "https://your-worker.your-subdomain.workers.dev/mcp"
  }
}
```
   
## Available Modules

The following modules are available to be enabled/disabled:

- `AI_OPTIMIZATION`: provides data for keyword discovery, conversational optimization, and real-time LLM benchmarking;
- `SERP`: real-time SERP data for Google, Bing, and Yahoo;
- `KEYWORDS_DATA`: keyword research and clickstream data;
- `ONPAGE`: crawl websites and webpages to obtain on-page SEO performance metrics;
- `DATAFORSEO_LABS`: data on keywords, SERPs, and domains based on DataForSEO's databases and algorithms;
- `BACKLINKS`: data on inbound links, referring domains and referring pages for any domain, subdomain, or webpage;
- `BUSINESS_DATA`: based on business reviews and business information publicly shared on the following platforms: Google, Trustpilot, Tripadvisor;
- `DOMAIN_ANALYTICS`: helps identify all possible technologies used for building websites and offers Whois data;
- `CONTENT_ANALYSIS`: help you discover citations of the target keyword or brand and analyze the sentiments around it;

## Adding New Tools/Modules

### Module Structure

Each module corresponds to a specific DataForSEO API:
- `AI_OPTIMIZATION`: [AI Optimization API](https://docs.dataforseo.com/v3/ai_optimization/overview)
- `SERP` module → [SERP API](https://docs.dataforseo.com/v3/serp/overview)
- `KEYWORDS_DATA` module → [Keywords Data API](https://docs.dataforseo.com/v3/keywords_data/overview)
- `ONPAGE` module → [OnPage API](https://docs.dataforseo.com/v3/on_page/overview)
- `DATAFORSEO_LABS` module → [DataForSEO Labs API](https://docs.dataforseo.com/v3/dataforseo_labs/overview)
- `BACKLINKS`: module → [Backlinks API](https://docs.dataforseo.com/v3/backlinks/overview)
- `BUSINESS_DATA`: module → [Business Data API](https://docs.dataforseo.com/v3/business_data/overview)
- `DOMAIN_ANALYTICS`: module → [Domain Analytics API](https://docs.dataforseo.com/v3/domain_analytics/overview)
- `CONTENT_ANALYSIS`: module → [Content Analysis API](https://docs.dataforseo.com/v3/content_analysis/overview)

### Implementation Options

You can either:
1. Add a new tool to an existing module
2. Create a completely new module

### Adding a New Tool

Here's how to add a new tool to any new or pre-existing module:

```typescript
// src/code/modules/your-module/tools/your-tool.tool.ts
import { BaseTool } from '../../base.tool';
import { DataForSEOClient } from '../../../client/dataforseo.client';
import { z } from 'zod';

export class YourTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
    // DataForSEO API returns extensive data with many fields, which can be overwhelming
    // for AI agents to process. We select only the most relevant fields to ensure
    // efficient and focused responses.
    this.fields = [
      'title',           // Example: Include the title field
      'description',     // Example: Include the description field
      'url',            // Example: Include the URL field
      // Add more fields as needed
    ];
  }

  getName() {
    return 'your-tool-name';
  }

  getDescription() {
    return 'Description of what your tool does';
  }

  getParams(): z.ZodRawShape {
    return {
      // Required parameters
      keyword: z.string().describe('The keyword to search for'),
      location: z.string().describe('Location in format "City,Region,Country" or just "Country"'),
      
      // Optional parameters
      fields: z.array(z.string()).optional().describe('Specific fields to return in the response. If not specified, all fields will be returned'),
      language: z.string().optional().describe('Language code (e.g., "en")'),
    };
  }

  async handle(params: any) {
    try {
      // Make the API call
      const response = await this.client.makeRequest({
        endpoint: '/v3/dataforseo_endpoint_path',
        method: 'POST',
        body: [{
          // Your request parameters
          keyword: params.keyword,
          location: params.location,
          language: params.language,
        }],
      });

      // Validate the response for errors
      this.validateResponse(response);

      //if the main data array is specified in tasks[0].result[:] field
      const result = this.handleDirectResult(response);
      //if main data array specified in tasks[0].result[0].items field
      const result = this.handleItemsResult(response);
      // Format and return the response
      return this.formatResponse(result);
    } catch (error) {
      // Handle and format any errors
      return this.formatErrorResponse(error);
    }
  }
}
```

### Creating a New Module

1. Create a new directory under `src/core/modules/` for your module:
```bash
mkdir -p src/core/modules/your-module-name
```

2. Create module files:
```typescript
// src/core/modules/your-module-name/your-module-name.module.ts
import { BaseModule } from '../base.module';
import { DataForSEOClient } from '../../client/dataforseo.client';
import { YourTool } from './tools/your-tool.tool';

export class YourModuleNameModule extends BaseModule {
  constructor(private client: DataForSEOClient) {
    super();
  }

  getTools() {
    return {
      'your-tool-name': new YourTool(this.client),
    };
  }
}
```

3. Register your module in `src/core/config/modules.config.ts`:
```typescript
export const AVAILABLE_MODULES = [
  'SERP',
  'KEYWORDS_DATA',
  'ONPAGE',
  'DATAFORSEO_LABS',
  'BACKLINKS',
  'BUSINESS_DATA',
  'DOMAIN_ANALYTICS',
  'CONTENT_ANALYSIS',
  'YOUR_MODULE_NAME'  // Add your module name here
] as const;
```

4. Initialize your module in `src/main/index.ts`:
```typescript
if (isModuleEnabled('YOUR_MODULE_NAME', enabledModules)) {
  modules.push(new YourModuleNameModule(dataForSEOClient));
}
```

## Field Configuration

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.

### Configuration File Format

Create a JSON configuration file with the following structure:

```json
{
  "supported_fields": {
    "tool_name": ["field1", "field2", "field3"],
    "another_tool": ["field1", "field2"]
  }
}
```

### Using Field Configuration

Pass the configuration file using the `--configuration` parameter:

```bash
# With npm
npm run cli -- http --configuration field-config.json

# With npx
npx dataforseo-mcp-server http --configuration field-config.json

# Local mode
npx dataforseo-mcp-server local --configuration field-config.json
```

### Configuration Behavior

- **If a tool is configured**: Only the specified fields will be returned in the response
- **If a tool is not configured**: All available fields will be returned (default behavior)
- **If no configuration file is provided**: All tools return all available fields

### Example Configuration File

The repository includes an example configuration file `field-config.example.json` with optimized field selections for common tools:

```json
{
  "supported_fields": {
    "backlinks_backlinks": [
      "id",
      "items.anchor",
      "items.backlink_spam_score",
      "items.dofollow",
      "items.domain_from",
      "items.domain_from_country",
      "items.domain_from_ip",
      "items.domain_from_platform_type",
      "items.domain_from_rank",
      "items.domain_to",
      "items.first_seen",
      "items.is_broken",
      "items.is_new",
      "items.item_type",
      "items.last_seen",
      "items.links_count",
      "items.original",
      "items.page_from_encoding",
      "items.page_from_external_links",
      "items.page_from_internal_links",
      "items.page_from_language",
      "items.page_from_rank",
      "items.page_from_size",
      "items.page_from_status_code",
      "items.page_from_title",
      "items.prev_seen",
      "items.rank",
      "items.ranked_keywords_info.page_from_keywords_count_top_10",
      "items.ranked_keywords_info.page_from_keywords_count_top_100",
      "items.ranked_keywords_info.page_from_keywords_count_top_3",
      "items.semantic_location",
      "items.text_post",
      "items.text_pre",
      "items.tld_from",
      "items.type",
      "items.url_from",
      "items.url_from_https",
      "items.url_to",
      "items.url_to_https",
      "items.url_to_spam_score",
      "items.url_to_status_code",
      "status_code",
      "status_message"
    ],
    ...
  }
}
```

### Nested Field Support

The configuration supports nested field paths using dot notation:

- `"rating.value"` - Access the `value` field within the `rating` object
- `"items.demography.age.keyword"` - Access deeply nested fields
- `"meta.description"` - Access nested object properties

### Field Discovery

To discover available fields for any tool:

1. Run the tool without field configuration to see the full response
2. Identify the fields you need from the API response
3. Add those field paths to your configuration file

### Creating Your Own Configuration

1. Copy the example file:
```bash
cp field-config.example.json my-config.json
```

2. Modify the field selections based on your needs

3. Use your custom configuration:
```bash
npx dataforseo-mcp-server http --configuration my-config.json
```

## What endpoints/APIs do you want us to support next?

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:

1. Check the [DataForSEO API Documentation](https://docs.dataforseo.com/v3/) to see what's available
2. Open an issue in our GitHub repository with:
   - The API/endpoint you'd like to see supported;
   - A brief description of your use case;
   - Describe any specific features you'd like to see implemented.

Your feedback helps us prioritize which APIs to support next!

## Resources

- [Model Context Protocol Documentation](https://modelcontextprotocol.io/quickstart)
- [DataForSEO API Documentation](https://docs.dataforseo.com/)
```

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

```dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
EXPOSE 3000
ENV NODE_ENV=production
ENTRYPOINT ["node", "build/main/main/cli.js"]
CMD ["http"]
```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build/main/",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "types": ["node"],
    "sourceMap": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "src/worker/**/*"]
}

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "bundler",
    "outDir": "./build/worker",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "types": ["@cloudflare/workers-types", "./src/worker/worker-configuration.d.ts"],
    "lib": ["ES2022", "WebWorker"]
  },
  "include": [
    "src/**/*.ts"
  ]
}

```

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

```typescript
import { z } from 'zod';

export interface PromptDefinition {
  name: string;
  title: string;
  description?: string;
  params?: z.ZodRawShape;
  handler: (args: any) => Promise<PromptResult>;
}

export interface PromptResult {
  [x: string]: unknown;
  description?: string;
  messages: PromptResultMessage[];
  _meta?: { [x: string]: unknown; };
}

export interface PromptResultMessage {
  role: "user" | "assistant";
  content: {
    type: "text";
    text: string;
  };

  [x: string]: any;
}
```

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

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

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

```typescript
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { BusinessDataBusinessListingsSearchTool } from './tools/listings/business-listings-search.tool.js';

export class BusinessDataApiModule extends BaseModule {
  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new BusinessDataBusinessListingsSearchTool(this.dataForSEOClient),
      // Add more tools here
    ];

    return tools.reduce((acc, tool) => ({
      ...acc,
      [tool.getName()]: {
        description: tool.getDescription(),
        params: tool.getParams(),
        handler: (params: any) => tool.handle(params),
      },
    }), {});
  }

    getPrompts(): Record<string, PromptDefinition> {
      return {}
    }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../../base.tool.js';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';

export class GoogleTrendsCategoriesTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'keywords_data_google_trends_categories';
  }

  getDescription(): string {
    return 'This endpoint will provide you list of Google Trends Categories';
  }

  getParams(): z.ZodRawShape {
    return {
      
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/google_trends/categories/live', 'GET');
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { debug } from 'console';
import { z } from 'zod';

export const GlobalToolConfigSchema = z.object({
  simpleFilter: z.boolean().default(false),
  fullResponse: z.boolean().default(false),
  debug: z.boolean().default(false)
});

export type GlobalToolConfig = z.infer<typeof GlobalToolConfigSchema>;

// Parse config from environment variables
export function parseGlobalToolConfig(): GlobalToolConfig {
  const fullResponseEnv = process.env.DATAFORSEO_FULL_RESPONSE as string;
  const debugEnv = process.env.DEBUG as string;
  const simpleFilterEnv = process.env.DATAFORSEO_SIMPLE_FILTER as string;
  const config = {
    fullResponse: fullResponseEnv === 'true',
    debug: debugEnv === 'true',
    simpleFilter: simpleFilterEnv === 'true'
  };
  
  return GlobalToolConfigSchema.parse(config);
}

// Export default config
export const defaultGlobalToolConfig = parseGlobalToolConfig(); 
```

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

```
const fs = require('fs');
const path = require('path');

// Read package.json
const packageJsonPath = path.join(__dirname, '../package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));

// Generate worker version file content
const content = `// Auto-generated from package.json - do not edit manually
// Generated on: ${new Date().toISOString()}

export const version = "${packageJson.version}";
export const name = "${packageJson.name}";

export default {
  version,
  name
};
`;

// Ensure the worker directory exists
const workerDir = path.join(__dirname, '../src/worker');
if (!fs.existsSync(workerDir)) {
  fs.mkdirSync(workerDir, { recursive: true });
}

// Write the version file
const outputPath = path.join(workerDir, 'version.worker.ts');
fs.writeFileSync(outputPath, content);

console.log(`✅ Generated worker version file with version ${packageJson.version}`);
console.log(`📁 Output: ${outputPath}`);

```

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

```typescript
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { AiOptimizationKeywordDataLocationsAndLanguagesListTool } from './tools/keyword-data/ai-optimization-keyword-data-locations-and-languages.js';
import { AiOptimizationKeywordDataSearchVolumeTool } from './tools/keyword-data/ai-optimization-keyword-data-search-volume.js'

export class AiOptimizationApiModule extends BaseModule {
  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new AiOptimizationKeywordDataLocationsAndLanguagesListTool(this.dataForSEOClient),
      new AiOptimizationKeywordDataSearchVolumeTool(this.dataForSEOClient),
    ];

    return tools.reduce((acc, tool) => ({
      ...acc,
      [tool.getName()]: {
        description: tool.getDescription(),
        params: tool.getParams(),
        handler: (params: any) => tool.handle(params),
      },
    }), {});
  }

    getPrompts(): Record<string, PromptDefinition> {
      return {};
    }
} 
```

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

```typescript
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { ContentAnalysisPhraseTrendsTool } from './tools/content-analysis-phrase-trends.js';
import { ContentAnalysisSearchTool } from './tools/content-analysis-search.tool.js';
import { ContentAnalysisSummaryTool } from './tools/content-analysis-summary.js';

export class ContentAnalysisApiModule extends BaseModule {
  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new ContentAnalysisSearchTool(this.dataForSEOClient),
      new ContentAnalysisSummaryTool(this.dataForSEOClient),
      new ContentAnalysisPhraseTrendsTool(this.dataForSEOClient),
      // Add more tools here
    ];

    return tools.reduce((acc, tool) => ({
      ...acc,
      [tool.getName()]: {
        description: tool.getDescription(),
        params: tool.getParams(),
        handler: (params: any) => tool.handle(params),
      },
    }), {});
  }

  getPrompts(): Record<string, PromptDefinition> {
    return {}
  }
} 
```

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

```typescript
import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
import { ZodRawShape } from 'zod';

export class AiOptimizationKeywordDataLocationsAndLanguagesListTool extends BaseTool {

  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  protected supportOnlyFullResponse(): boolean {
    return true;
  }
  
  getName(): string {
      return "ai_optimization_keyword_data_locations_and_languages";
  }

  getDescription(): string {
      return "Utility tool for ai_keyword_data_search_volume to get list of availible locations and languages";
  }

  getParams(): ZodRawShape {
     return {};
  }

  async handle(params: any): Promise<any> {
     try {

      const response = await this.dataForSEOClient.makeRequest(`/v3/ai_optimization/ai_keyword_data/locations_and_languages`, 'GET', null);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }

}
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../base.tool.js';

export class DomainTechnologiesTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'domain_analytics_technologies_domain_technologies';
  }

  getDescription(): string {
    return `Using this endpoint you will get a list of technologies used in a particular domain`;
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`target domain
required field
domain name of the website to analyze
Note: results will be returned for the specified domain only`)      
      }
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/domain_analytics/technologies/domain_technologies/live', 'POST', [{
        target: params.target
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { DomainTechnologiesTool } from './tools/technologies/domain-technologies.tool.js';
import { DomainTechnologiesFiltersTool } from './tools/technologies/domain-technologies-filters.tool.js';
import { WhoisFiltersTool } from './tools/whois/whois-filters.tool.js';
import { WhoisOverviewTool } from './tools/whois/whois-overview.tool.js';

export class DomainAnalyticsApiModule extends BaseModule {
  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new WhoisOverviewTool(this.dataForSEOClient),
      new WhoisFiltersTool(this.dataForSEOClient),
      new DomainTechnologiesTool(this.dataForSEOClient),
      new DomainTechnologiesFiltersTool(this.dataForSEOClient),
      // Add more tools here
    ];

    return tools.reduce((acc, tool) => ({
      ...acc,
      [tool.getName()]: {
        description: tool.getDescription(),
        params: tool.getParams(),
        handler: (params: any) => tool.handle(params),
      },
    }), {});
  }

  getPrompts(): Record<string, PromptDefinition> {
    return {};
  }
} 
```

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

```typescript
import { DataForSEOClient } from '../client/dataforseo.client.js';
import { z } from 'zod';
import { PromptDefinition } from './prompt-definition.js';

export interface ToolDefinition {
  description: string;
  params: z.ZodRawShape;
  handler: (params: any) => Promise<any>;
}

export abstract class BaseModule {
  protected dataForSEOClient: DataForSEOClient;

  constructor(dataForSEOClient: DataForSEOClient) {
    this.dataForSEOClient = dataForSEOClient;
  }

  protected formatError(error: unknown): string {
    return error instanceof Error ? error.message : 'Unknown error';
  }

  protected formatResponse(data: any): { content: Array<{ type: string; text: string }> } {
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(data, null, 2),
        },
      ],
    };
  }

  protected formatErrorResponse(error: unknown): { content: Array<{ type: string; text: string }> } {
    return {
      content: [
        {
          type: "text",
          text: `Error: ${this.formatError(error)}`,
        },
      ],
    };
  }

  abstract getTools(): Record<string, ToolDefinition>;

  abstract getPrompts(): Record<string, PromptDefinition>;
} 
```

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

```typescript
import { z } from 'zod';

// Define available module names
export const AVAILABLE_MODULES = ['AI_OPTIMIZATION', 'SERP', 'KEYWORDS_DATA', 'ONPAGE', 'DATAFORSEO_LABS', 'BACKLINKS', 'BUSINESS_DATA', 'DOMAIN_ANALYTICS', 'CONTENT_ANALYSIS'] as const;
export type ModuleName = typeof AVAILABLE_MODULES[number];

// Schema for validating the ENABLED_MODULES environment variable
export const EnabledModulesSchema = z.any()
  .transform((val:string) => {
    if (!val) return AVAILABLE_MODULES; // If not set, enable all modules
    return val.toString().split(',').map(name => name.trim().toUpperCase() as ModuleName);
  })
  .refine((modules) => {
    return modules.every(module => AVAILABLE_MODULES.includes(module));
  }, {
    message: `Invalid module name. Available modules are: ${AVAILABLE_MODULES.join(', ')}`
  });

export type EnabledModules = z.infer<typeof EnabledModulesSchema>;

// Helper function to check if a module is enabled
export function isModuleEnabled(moduleName: ModuleName, enabledModules: EnabledModules): boolean {
  return enabledModules.includes(moduleName);
}

// Default configuration (all modules enabled)
export const defaultEnabledModules: EnabledModules = AVAILABLE_MODULES; 
```

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

```typescript
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { onpagePrompts } from './onpage.prompt.js';
import { ContentParsingTool } from './tools/content-parsing.tool.js';
import { InstantPagesTool } from './tools/instant-pages.tool.js';
import { LighthouseTool } from './tools/lighthouse.tool.js';

export class OnPageApiModule extends BaseModule {
  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new ContentParsingTool(this.dataForSEOClient),
      new InstantPagesTool(this.dataForSEOClient),
      new LighthouseTool(this.dataForSEOClient),
      // Add more tools here
    ];

    return tools.reduce((acc, tool) => ({
      ...acc,
      [tool.getName()]: {
        description: tool.getDescription(),
        params: tool.getParams(),
        handler: (params: any) => tool.handle(params),
      },
    }), {});
  }

    getPrompts(): Record<string, PromptDefinition> {
      return onpagePrompts.reduce((acc, prompt) => ({
        ...acc,
        [prompt.name]: {
          description: prompt.description,
          params: prompt.params,
          handler: (params: any) => {

            return prompt.handler(params);
          },
        },
      }), {});
    }
} 

```

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

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { DataForSEOClient, DataForSEOConfig } from '../core/client/dataforseo.client.js';
import { EnabledModulesSchema, isModuleEnabled, defaultEnabledModules } from '../core/config/modules.config.js';
import { BaseModule, ToolDefinition } from '../core/modules/base.module.js';
import { z } from 'zod';
import { ModuleLoaderService } from "../core/utils/module-loader.js";
import { initializeFieldConfiguration } from '../core/config/field-configuration.js';
import { name, version } from '../core/utils/version.js';
import { initMcpServer } from "./init-mcp-server.js";

// Initialize field configuration if provided
initializeFieldConfiguration();
console.error('Starting DataForSEO MCP Server...');
console.error(`Server name: ${name}, version: ${version}`);

const server = initMcpServer(process.env.DATAFORSEO_USERNAME, process.env.DATAFORSEO_PASSWORD);

// Start the server
async function main() {
  const transport = new StdioServerTransport(); 
  console.error('Starting server');
  await server.connect(transport);
  console.error("DataForSEO MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../../base.tool.js';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';

export class GoogleAdsSearchVolumeTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'keywords_data_google_ads_search_volume';
  }

  getDescription(): string {
    return 'Get search volume data for keywords from Google Ads';
  }

  getParams(): z.ZodRawShape {
    return {
      location_name: z.string().nullable().default(null).describe(`full name of the location
optional field
in format "Country"
example:
United Kingdom`),
              language_code: z.string().nullable().default(null).describe(`Language two-letter ISO code (e.g., 'en').
optional field`),
      keywords: z.array(z.string()).describe("Array of keywords to get search volume for"),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/google_ads/search_volume/live', 'POST', [{
        location_name: params.location_name,
        language_code: params.language_code,
        keywords: params.keywords,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { defaultGlobalToolConfig } from '../config/global.tool.js';

export class DataForSEOClient {
  private config: DataForSEOConfig;
  private authHeader: string;

  constructor(config: DataForSEOConfig) {
    this.config = config;
    if(defaultGlobalToolConfig.debug) {
      console.error('DataForSEOClient initialized with config:', config);
    }
    const token = btoa(`${config.username}:${config.password}`);
    this.authHeader = `Basic ${token}`;
  }

  async makeRequest<T>(endpoint: string, method: string = 'POST', body?: any, forceFull: boolean = false): Promise<T> {
    let url = `${this.config.baseUrl || "https://api.dataforseo.com"}${endpoint}`;    
    if(!defaultGlobalToolConfig.fullResponse && !forceFull){
      url += '.ai';
    }
    // Import version dynamically to avoid circular dependencies
    const { version } = await import('../utils/version.js');
    
    const headers = {
      'Authorization': this.authHeader,
      'Content-Type': 'application/json',
      'User-Agent': `DataForSEO-MCP-TypeScript-SDK/${version}`
    };

    console.error(`Making request to ${url} with method ${method} and body`, body);
    const response = await fetch(url, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }
} 

export interface DataForSEOConfig {
  username: string;
  password: string;
  baseUrl?: string;
}
```

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

```typescript
import { z } from 'zod';
import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
import { ZodRawShape } from 'zod';

export class AiOptimizationKeywordDataSearchVolumeTool extends BaseTool {

  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }
  
  getName(): string {
      return "ai_optimization_keyword_data_search_volume";
  }

  getDescription(): string {
      return "This endpoint provides search volume data for your target keywords, reflecting their estimated usage in AI LLMs";
  }

  getParams(): z.ZodRawShape {
    return {
      keywords: z.array(z.string()).describe("Keywords. The maximum number of keywords you can specify: 1000"),
      location_name: z.string().default('United States').describe(`full name of the location, example: 'United Kingdom', 'United States'`),
      language_code: z.string().describe("Search engine language code (e.g., 'en')"),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      console.error(JSON.stringify(params, null, 2));
      const response = await this.dataForSEOClient.makeRequest(`/v3/ai_optimization/ai_keyword_data/keywords_search_volume/live`, 'POST', [{
        keywords: params.keywords,
        location_name: params.location_name,
        language_code: params.language_code
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }

}
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksBulkSpamScoreTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_spam_score';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
required field
you can set up to 1000 domains, subdomains or webpages
the domain or subdomain should be specified without https:// and www.
the page should be specified with absolute URL (including http:// or https://)
example:
"targets": [
"forbes.com",
"cnn.com",
"bbc.com",
"yelp.com",
"https://www.apple.com/iphone/",
"https://ahrefs.com/blog/",
"ibm.com",
"https://variety.com/",
"https://stackoverflow.com/",
"www.trustpilot.com"
]`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_spam_score/live', 'POST', [{
        targets: params.targets
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOResponse } from '../../base.tool.js';

export class BacklinksBulkSpamScoreTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_spam_score_tool';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
required field
you can set up to 1000 domains, subdomains or webpages
the domain or subdomain should be specified without https:// and www.
the page should be specified with absolute URL (including http:// or https://)
example:
"targets": [
"forbes.com",
"cnn.com",
"bbc.com",
"yelp.com",
"https://www.apple.com/iphone/",
"https://ahrefs.com/blog/",
"ibm.com",
"https://variety.com/",
"https://stackoverflow.com/",
"www.trustpilot.com"
]`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_spam_score/live', 'POST', [{
        targets: params.targets
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { DataForSeoTrendsDemographyTool } from './tools/dataforseo-trends/dataforseo-trends-demography.tool.js';
import { DataForSeoTrendsExploreTool } from './tools/dataforseo-trends/dataforseo-trends-explore.tool.js';
import { DataForSeoTrendsSubregionInterestsTool } from './tools/dataforseo-trends/dataforseo-trends-subregion-interests.tool.js';
import { GoogleAdsSearchVolumeTool } from './tools/google-ads/google-ads-search-volume.tool.js';
import { GoogleTrendsCategoriesTool } from './tools/google-trends/google-trends-categories.tool.js';
import { GoogleTrendsExploreTool } from './tools/google-trends/google-trends-explore.tool.js';

export class KeywordsDataApiModule extends BaseModule {
  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new GoogleAdsSearchVolumeTool(this.dataForSEOClient),

      new DataForSeoTrendsDemographyTool(this.dataForSEOClient),
      new DataForSeoTrendsSubregionInterestsTool(this.dataForSEOClient),
      new DataForSeoTrendsExploreTool(this.dataForSEOClient),

      new GoogleTrendsCategoriesTool(this.dataForSEOClient),
      new GoogleTrendsExploreTool(this.dataForSEOClient),
      // Add more tools here
    ];

    return tools.reduce((acc, tool) => ({
      ...acc,
      [tool.getName()]: {
        description: tool.getDescription(),
        params: tool.getParams(),
        handler: (params: any) => tool.handle(params),
      },
    }), {});
  }

  getPrompts(): Record<string, PromptDefinition> {
    return {};
  }
} 
```

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

```typescript
#!/usr/bin/env node

import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const args = process.argv.slice(2);
// Parse command line arguments
const mode = args[0] || 'local';
const configIndex = args.indexOf('--configuration');
const configPath = configIndex !== -1 && configIndex + 1 < args.length ? args[configIndex + 1] : null;
const debugLog = args.includes('--debug') || args.includes('-d');
// Set environment variable for configuration path if provided
if (configPath) {
    process.env.FIELD_CONFIG_PATH = configPath;
    console.error(`Using field configuration: ${configPath}`);
}
if( debugLog) {
    console.error('Debug mode enabled');
    process.env.DEBUG = 'true';
}
// Prepare arguments to pass to the spawned process (excluding --configuration args)
const argsWithoutMode = args.slice(1);
const childArgs = argsWithoutMode.filter((_, index) => {
    return index !== configIndex - 1 && index !== configIndex;});
    
if (mode === 'http') {
    const httpServer = join(__dirname, 'index-http.js');
    spawn('node', [httpServer, ...childArgs], { 
        stdio: 'inherit',
        env: { ...process.env }
    });
} else if (mode === 'sse') {
    const sseServer = join(__dirname, 'index-sse-http.js');
    spawn('node', [sseServer, ...childArgs], { 
        stdio: 'inherit',
        env: { ...process.env }
    });
} else {
    const localServer = join(__dirname, 'index.js');
    spawn('node', [localServer, ...childArgs], { 
        stdio: 'inherit',
        env: { ...process.env }
    });
}
```

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

```typescript
import { z } from 'zod';
import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { DataForSEOResponse } from '../../base.tool.js';
import { defaultGlobalToolConfig } from '../../../config/global.tool.js';

export class LighthouseTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'on_page_lighthouse';
  }

  getDescription(): string {
    return 'The OnPage Lighthouse API is based on Google’s open-source Lighthouse project for measuring the quality of web pages and web apps.';
  }

  getParams(): z.ZodRawShape {
    return {
      url: z.string().describe("URL of the page to parse"),
      enable_javascript: z.boolean().optional().describe("Enable JavaScript rendering"),
      custom_js: z.string().optional().describe("Custom JavaScript code to execute"),
      custom_user_agent: z.string().optional().describe("Custom User-Agent header"),
      accept_language: z.string().optional().describe("Accept-Language header value"),
    };
  }

  async handle(params: any): Promise<any> {
    try {
        const response = await this.dataForSEOClient.makeRequest('/v3/on_page/lighthouse/live/json', 'POST', [{
          url: params.url,
          enable_javascript: params.enable_javascript,
          custom_js: params.custom_js,
          custom_user_agent: params.custom_user_agent,
          accept_language: params.accept_language,
        }]);
        return this.validateAndFormatResponse(response);
      } catch (error) {
        return this.formatErrorResponse(error);
      }
  }
} 

```

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

```typescript
// Environment detection and version loading for both Node.js and Workers
let packageVersion = '1.0.0'; // Default version
let packageName = 'dataforseo-mcp-server'; // Default name

// Type declarations for worker environment globals
declare global {
  var __PACKAGE_VERSION__: string | undefined;
  var __PACKAGE_NAME__: string | undefined;
}

// Check if we're in a Node.js environment (has fs module)
const isNodeEnvironment = typeof globalThis !== 'undefined' && 
  typeof globalThis.process !== 'undefined' && 
  globalThis.process.versions?.node;

if (isNodeEnvironment) {
  // Node.js environment - read from package.json
  try {
    const fs = await import('fs');
    const path = await import('path');
    const { fileURLToPath } = await import('url');

    // Get the directory of the current module
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);

    const packageJsonPath = path.resolve(__dirname, '../../../../package.json');
      const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
    packageVersion = packageJson.version || packageVersion;
    packageName = packageJson.name || packageName;
  } catch (error: unknown) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    console.warn('Could not read package.json, using default version:', errorMessage);
  }
} else {
  // Worker environment - use compile-time constants
  // These will be replaced by the build process or use defaults
  packageVersion = globalThis.__PACKAGE_VERSION__ || packageVersion;
  packageName = globalThis.__PACKAGE_NAME__ || packageName;
}

export const version = packageVersion;
export const name = packageName;

export default {
  version,
  name
};
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../../base.tool.js';

export class GoogleDomainRankOverviewTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_google_domain_rank_overview';
  }

  getDescription(): string {
    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.`;
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`target domain`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`),
      ignore_synonyms: z.boolean().default(true).describe(
          `ignore highly similar keywords, if set to true, results will be more accurate`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/domain_rank_overview/live', 'POST', [{
        target: params.target,
        location_name: params.location_name,
        language_code: params.language_code,
        ignore_synonyms: params.ignore_synonyms
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```json
{
  "name": "dataforseo-mcp-server",
  "version": "2.7.12",
  "main": "build/main/main/index.js",
  "type": "module",
  "bin": {
    "dataforseo-mcp-server": "./build/main/main/cli.js"
  },
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/main/main/cli.js', '755')\"",
    "start": "node build/main/main/index.js",
    "dev": "tsc --watch",
    "prepare": "npm run build",
    "http": "node build/main/main/index-http.js",
    "sse": "node build/main/main/index-sse-http.js",
    "cli": "node build/main/main/cli.js",
    "worker:prebuild": "node scripts/generate-worker-version.cjs && wrangler types ./src/worker/worker-configuration.d.ts",
    "worker:build": "npm run worker:prebuild && tsc --project tsconfig.worker.json",
    "worker:dev": "npm run worker:build && wrangler dev",
    "worker:deploy": "npm run worker:build && wrangler deploy"
  },
  "files": [
    "build"
  ],
  "keywords": [
    "dataforseo",
    "mcp",
    "modelcontextprotocol",
    "api",
    "server",
    "seo",
    "sse",
    "streaming"
  ],
  "author": "dataforseo",
  "license": "Apache-2.0",
  "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.",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/dataforseo/mcp-server-typescript.git"
  },
  "homepage": "https://github.com/dataforseo/mcp-server-typescript#readme",
  "engines": {
    "node": ">=20.0.0"
  },
  "devDependencies": {
    "@types/express": "^5.0.1",
    "@types/node": "^22.10.0",
    "typescript": "^5.7.2"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.4.0",
    "agents": "^0.0.101",
    "express": "^4.18.2",
    "wrangler": "^4.24.0",
    "zod": "^3.25.67"
  }
}

```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksBulkBacklinksTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_backlinks';
  }

  getDescription(): string {
    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.
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`;
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
required field
you can set up to 1000 domains, subdomains or webpages
the domain or subdomain should be specified without https:// and www.
the page should be specified with absolute URL (including http:// or https://)
example:
"targets": [
"forbes.com",
"cnn.com",
"bbc.com",
"yelp.com",
"https://www.apple.com/iphone/",
"https://ahrefs.com/blog/",
"ibm.com",
"https://variety.com/",
"https://stackoverflow.com/",
"www.trustpilot.com"
]`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_backlinks/live', 'POST', [{
        targets: params.targets
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../../base.tool.js';

export class GoogleSearchIntentTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_search_intent';
  }

  getDescription(): string {
    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.
Based on keyword data and search results data, our system has been trained to detect four types of search intent: informational, navigational, commercial, transactional.`;
  }

  getParams(): z.ZodRawShape {
    return {
      keywords: z.array(z.string()).describe(`target keywords
required field
UTF-8 encoding
maximum number of keywords you can specify in this array: 1000`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        Note: this endpoint currently supports the following languages only:
ar,
zh-TW,
cs,
da,
nl,
en,
fi,
fr,
de,
he,
hi,
it,
ja,
ko,
ms,
nb,
pl,
pt,
ro,
ru,
es,
sv,
th,
uk,
vi,
bg,
hr,
sr,
sl,
bs`),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/search_intent/live', 'POST', [{
        keywords: params.keywords,
        language_code: params.language_code
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../../base.tool.js';

export class GoogleBulkKeywordDifficultyTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_bulk_keyword_difficulty';
  }

  getDescription(): string {
    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.`;
  }

  getParams(): z.ZodRawShape {
    return {
      keywords: z.array(z.string()).describe(`target keywords
required field
UTF-8 encoding
maximum number of keywords you can specify in this array: 1000`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/bulk_keyword_difficulty/live', 'POST', [{
        keywords: params.keywords,
        location_name: params.location_name,
        language_code: params.language_code
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksBulkReferringDomainsTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_referring_domains';
  }

  getDescription(): string {
    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.
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`;
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
required field
you can set up to 1000 domains, subdomains or webpages
the domain or subdomain should be specified without https:// and www.
the page should be specified with absolute URL (including http:// or https://)
example:
"targets": [
"forbes.com",
"cnn.com",
"bbc.com",
"yelp.com",
"https://www.apple.com/iphone/",
"https://ahrefs.com/blog/",
"ibm.com",
"https://variety.com/",
"https://stackoverflow.com/",
"www.trustpilot.com"
]`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_referring_domains/live', 'POST', [{
        targets: params.targets
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksBulkPagesSummaryTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_pages_summary';
  }

  getDescription(): string {
    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.";
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get summary data for
required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)
you can specify up to 1000 pages, domains, or subdomains in each request.
note that the URLs you set in a single request cannot belong to more than 100 different domains.`),
      include_subdomains: z.boolean().optional().describe(`indicates if indirect links to the target will be included in the results
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
if set to false, indirect links will be ignored`).default(true)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_pages_summary/live', 'POST', [{
        targets: params.targets,
        include_subdomains: params.include_subdomains,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class InstantPagesTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'on_page_instant_pages';
  }

  getDescription(): string {
    return "Using this function you will get page-specific data with detailed information on how well a particular page is optimized for organic search";
  }

  getParams(): z.ZodRawShape {
    return {
      url: z.string().describe("URL to analyze"),
      enable_javascript: z.boolean().optional().describe("Enable JavaScript rendering"),
      custom_js: z.string().optional().describe("Custom JavaScript code to execute"),
      custom_user_agent: z.string().optional().describe("Custom User-Agent header"),
      accept_language: z.string().optional().describe(`language header for accessing the website
        all locale formats are supported (xx, xx-XX, xxx-XX, etc.)
        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`),
    };
  }

  async handle(params: { 
    url: string; 
    enable_javascript?: boolean; 
    custom_js?: string; 
    custom_user_agent?: string; 
    accept_language?: string; 
  }): Promise<any> {
    try {
        const response = await this.dataForSEOClient.makeRequest('/v3/on_page/instant_pages', 'POST', [{
          url: params.url,
          enable_javascript: params.enable_javascript,
          custom_js: params.custom_js,
          custom_user_agent: params.custom_user_agent,
          accept_language: params.accept_language,
        }]);
        return this.validateAndFormatResponse(response);
      } catch (error) {
        return this.formatErrorResponse(error);
      }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksSummaryTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_summary';
  }

  getDescription(): string {
    return "This endpoint will provide you with an overview of backlinks data available for a given domain, subdomain, or webpage";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain, subdomain or webpage to get backlinks for
        required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      include_subdomains: z.boolean().optional().describe(`indicates if indirect links to the target will be included in the results
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
if set to false, indirect links will be ignored`).default(true),
      exclude_internal_backlinks: z.boolean().optional().describe(`indicates if internal backlinks from subdomains to the target will be excluded from the results
if set to true, the results will not include data on internal backlinks from subdomains of the same domain as target
if set to false, internal links will be included in the results`).default(true)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/summary/live', 'POST', [{
        target: params.target,
        include_subdomains: params.include_subdomains,
        exclude_internal_backlinks: params.exclude_internal_backlinks
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import readline from "readline/promises";

class MCPClient {
  private mcp: Client;
  private transport: StdioClientTransport | null = null;

  constructor() {
    // Initialize Anthropic client and MCP client
    this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
  }

  async connectToServer(serverScriptPath: string) {
    /**
     * Connect to an MCP server
     *
     * @param serverScriptPath - Path to the server script (.py or .js)
     */
    try {
      // Determine script type and appropriate command
      const isJs = serverScriptPath.endsWith(".js");
      const isPy = serverScriptPath.endsWith(".py");
      if (!isJs && !isPy) {
        throw new Error("Server script must be a .js or .py file");
      }
      const command = isPy
        ? process.platform === "win32"
          ? "python"
          : "python3"
        : process.execPath;

      // Initialize transport and connect to server
      this.transport = new StdioClientTransport({
        command,
        args: [serverScriptPath],
      });
      this.mcp.connect(this.transport);

      // List available tools
      const toolsResult = await this.mcp.listTools();
      console.log(toolsResult);
      console.log(
        "Connected to server with tools:",
        toolsResult.tools.map(({ name }) => name),
      );
    } catch (e) {
      console.log("Failed to connect to MCP server: ", e);
      throw e;
    }
  }

  async cleanup() {
    /**
     * Clean up resources
     */
    await this.mcp.close();
  }
}

async function main() {
  if (process.argv.length < 3) {
    console.log("Usage: node build/test.js <path_to_server_script>");
    return;
  }
  const mcpClient = new MCPClient();
  try {
    await mcpClient.connectToServer(process.argv[2]);
  } finally {
    await mcpClient.cleanup();
    process.exit(0);
  }
}

main();

```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOResponse } from '../../../../base.tool.js';

export class GoogleHistoricalDomainRankOverviewTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_google_historical_rank_overview';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`target domain`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`),
      ignore_synonyms: z.boolean().default(true).describe(
          `ignore highly similar keywords, if set to true, results will be more accurate`),        
      include_clickstream_data: z.boolean().optional().default(false).describe(
        `Include or exclude data from clickstream-based metrics in the result`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/historical_rank_overview/live', 'POST', [{
        target: params.target,
        location_name: params.location_name,
        language_code: params.language_code,
        ignore_synonyms: params.ignore_synonyms,
        include_clickstream_data: params.include_clickstream_data
      }]);
      return this.validateAndFormatResponse(response);

    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';

export class SerpYoutubeVideoInfoLiveAdvancedTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'serp_youtube_video_info_live_advanced';
  }

  getDescription(): string {
    return 'provides data on the video you specify';
  }

  getParams(): z.ZodRawShape {
    return {
      video_id: z.string().describe("ID of the video"),
      location_name: z.string().describe(`full name of the location
required field
Location format - hierarchical, comma-separated (from most specific to least)
 Can be one of:
 1. Country only: "United States"
 2. Region,Country: "California,United States"
 3. City,Region,Country: "San Francisco,California,United States"`),
      language_code: z.string().describe("search engine language code (e.g., 'en')"),
      device: z.string().default('desktop').optional().describe(`device type
optional field
can take the values:desktop, mobile
default value: desktop`),
      os: z.string().default('windows').optional().describe(`device operating system
optional field
if you specify desktop in the device field, choose from the following values: windows, macos
default value: windows
if you specify mobile in the device field, choose from the following values: android, ios
default value: android`)
    };
  }

  async handle(params:any): Promise<any> {
    try {
      console.error(JSON.stringify(params, null, 2));
      const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/video_info/live/advanced`, 'POST', [{
        video_id: params.video_id,
        location_name: params.location_name,
        language_code: params.language_code,
        device: params.device,
        os: params.os,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { DataForSEOResponse } from '../../base.tool.js';

export class SerpOrganicLocationsListTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'serp_locations';
  }

  getDescription(): string {
    return 'Utility tool for serp_organic_live_advanced to get list of availible locations.';
  }
  
  getParams(): z.ZodRawShape {
    return {
      search_engine: z.string().default('google').describe("search engine name, one of: google, yahoo, bing."),
      country_iso_code: z.string().describe("ISO 3166-1 alpha-2 country code, for example: US, GB, MT"),
      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'"),
      location_name: z.string().optional().describe("Name of location or it`s part.")
    };
  }

  async handle(params:any): Promise<any> {
    try {

      const payload: Record<string, unknown> = {
        'country_iso_code': params.country_iso_code,
      };

      if (params.location_type) {
        payload['location_type'] = params.location_type;
      }
      
      if (params.location_name) {
        payload['location_name'] = params.location_name;
      }

      const response = await this.dataForSEOClient.makeRequest(`/v3/serp/${params.search_engine}/locations`, 'POST', [payload]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { DataForSEOResponse } from '../../base.tool.js';


export class SerpYoutubeLocationsListTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'serp_youtube_locations';
  }

  getDescription(): string {
    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.';
  }

  getParams(): z.ZodRawShape {
    return {
      country_iso_code: z.string().describe("ISO 3166-1 alpha-2 country code, for example: US, GB, MT"),
      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'"),
      location_name: z.string().optional().describe("Name of location or it`s part.")
    };
  }

  async handle(params:any): Promise<any> {
    try {

     const payload: Record<string, unknown> = {
        'country_iso_code': params.country_iso_code,
      };

      if (params.location_type) {
        payload['location_type'] = params.location_type;
      }

      if (params.location_name) {
        payload['location_name'] = params.location_name;
      }

      const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/locations`, 'POST', [payload]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../../base.tool.js';

export class GoogleBulkTrafficEstimationTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_bulk_traffic_estimation';
  }

  getDescription(): string {
    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.`;
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`target domains, subdomains, and webpages.
        you can specify domains, subdomains, and webpages in this field;
domains and subdomains should be specified without https:// and www.;
pages should be specified with absolute URL, including https:// and www.;
you can set up to 1000 domains, subdomains or webpages`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`),
      ignore_synonyms: z.boolean().default(true).describe(
  `ignore highly similar keywords, if set to true, results will be more accurate`),

    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/bulk_traffic_estimation/live', 'POST', [{
        targets: params.targets,
        location_name: params.location_name,
        language_code: params.language_code,
        item_types: ['organic'],
        ignore_synonyms: params.ignore_synonyms
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { z } from 'zod';
import { SerpOrganicLiveAdvancedTool } from './tools/serp-organic-live-advanced.tool.js';
import { SerpOrganicLocationsListTool } from './tools/serp-organic-locations-list.tool.js';
import { SerpYoutubeOrganicLiveAdvancedTool } from './tools/serp-youtube-organic-live-advanced.tool.js';
import { SerpYoutubeVideoInfoLiveAdvancedTool } from './tools/serp-youtube-video-info-live-advanced.tool.js';
import { SerpYoutubeVideoCommentsLiveAdvancedTool } from './tools/serp-youtube-video-comments-live-advanced-tool.js';
import { SerpYoutubeVideoSubtitlesLiveAdvancedTool } from './tools/serp-youtube-video-subtitles-live-advanced-tool.js';
import { SerpYoutubeLocationsListTool } from './tools/serp-youtube-locations-list.tool.js';
import { serpPrompts } from './serp.prompt.js';

export class SerpApiModule extends BaseModule {
  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new SerpOrganicLiveAdvancedTool(this.dataForSEOClient),
      new SerpOrganicLocationsListTool(this.dataForSEOClient),

      new SerpYoutubeLocationsListTool(this.dataForSEOClient),
      new SerpYoutubeOrganicLiveAdvancedTool(this.dataForSEOClient),
      new SerpYoutubeVideoInfoLiveAdvancedTool(this.dataForSEOClient),
      new SerpYoutubeVideoCommentsLiveAdvancedTool(this.dataForSEOClient),
      new SerpYoutubeVideoSubtitlesLiveAdvancedTool(this.dataForSEOClient),
      // Add more tools here
    ];

    return tools.reduce((acc, tool) => ({
      ...acc,
      [tool.getName()]: {
        description: tool.getDescription(),
        params: tool.getParams(),
        handler: (params: any) => tool.handle(params),
      },
    }), {});
  }

  getPrompts(): Record<string, PromptDefinition> {
    return serpPrompts.reduce((acc, prompt) => ({
      ...acc,
      [prompt.name]: {
        description: prompt.description,
        params: prompt.params,
        handler: (params: any) => {

          return prompt.handler(params);
        },
      },
    }), {});
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOResponse } from '../../../../base.tool.js';

export class GoogleHistoricalKeywordDataTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_google_historical_keyword_data';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      keywords: z.array(z.string()).describe(`keywords
required field
The maximum number of keywords you can specify: 700
The maximum number of characters for each keyword: 80
The maximum number of words for each keyword phrase: 10
the specified keywords will be converted to lowercase format, data will be provided in a separate array
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
you will not be charged for the keywords omitted in the results`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/historical_keyword_data/live', 'POST', [{
        keywords: params.keywords,
        location_name: params.location_name,
        language_code: params.language_code
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksBulkRanksTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_ranks';
  }

  getDescription(): string {
    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";
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
required field
you can set up to 1000 domains, subdomains or webpages
the domain or subdomain should be specified without https:// and www.
the page should be specified with absolute URL (including http:// or https://)
example:
"targets": [
"forbes.com",
"cnn.com",
"bbc.com",
"yelp.com",
"https://www.apple.com/iphone/",
"https://ahrefs.com/blog/",
"ibm.com",
"https://variety.com/",
"https://stackoverflow.com/",
"www.trustpilot.com"
]`),
      rank_scale: z.string().optional().describe(`defines the scale used for calculating and displaying the rank, domain_from_rank, and page_from_rank values
optional field
you can use this parameter to choose whether rank values are presented on a 0–100 or 0–1000 scale
possible values:
one_hundred — rank values are displayed on a 0–100 scale
one_thousand — rank values are displayed on a 0–1000 scale`).default('one_thousand')
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_ranks/live', 'POST', [{
        targets: params.targets,
        rank_scale: params.rank_scale        
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { DataForSEOClient, DataForSEOConfig } from "../core/client/dataforseo.client.js";
import { EnabledModulesSchema } from "../core/config/modules.config.js";
import { ModuleLoaderService } from "../core/utils/module-loader.js";
import { BaseModule, ToolDefinition } from "../core/modules/base.module.js";
import { z } from 'zod';
import { name, version } from '../core/utils/version.js';


export function initMcpServer(username: string | undefined, password: string | undefined): McpServer {
  const server = new McpServer({
    name,
    version,
  }, { capabilities: { logging: {} } });

  // Initialize DataForSEO client
  const dataForSEOConfig: DataForSEOConfig = {
    username: username || "",
    password: password || "",
  };
  
  const dataForSEOClient = new DataForSEOClient(dataForSEOConfig);
  console.error('DataForSEO client initialized');
  
  // Parse enabled modules from environment
  const enabledModules = EnabledModulesSchema.parse(process.env.ENABLED_MODULES);
  
  // Initialize modules
  const modules: BaseModule[] = ModuleLoaderService.loadModules(dataForSEOClient, enabledModules);
  
  const enabledPrompts = process.env.ENABLED_PROMPTS?.split(',').map(name => name.trim()) || [];

  // Register modules
  modules.forEach(module => {
    
    const tools = module.getTools();
    Object.entries(tools).forEach(([name, tool]) => {
      const typedTool = tool as ToolDefinition;
      const schema = z.object(typedTool.params);
      server.tool(
        name,
        typedTool.description,
        schema.shape,
        typedTool.handler
      );
    });

    const prompts = module.getPrompts();
    // Filter prompts based on enabledPrompts configuration
    const allowedPrompts = enabledPrompts.length === 0
        ? prompts
        : Object.fromEntries(Object.entries(prompts).filter(([promptName]) => enabledPrompts.includes(promptName)));

    Object.entries(allowedPrompts).forEach(([name, prompt]) => {
      server.registerPrompt(
        name,
        {
          description: prompt.description,
          argsSchema: prompt.params,
        },
        prompt.handler
      );
    });
  });


  return server;
}
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';

export class SerpYoutubeOrganicLiveAdvancedTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'serp_youtube_organic_live_advanced';
  }

  getDescription(): string {
    return 'provides top 20 blocks of youtube search engine results for a keyword';
  }

  getParams(): z.ZodRawShape {
    return {
      keyword: z.string().describe("Search keyword"),
      location_name: z.string().describe(`full name of the location
required field
Location format - hierarchical, comma-separated (from most specific to least)
 Can be one of:
 1. Country only: "United States"
 2. Region,Country: "California,United States"
 3. City,Region,Country: "San Francisco,California,United States"`),
      language_code: z.string().describe("search engine language code (e.g., 'en')"),
      device: z.string().default('desktop').optional().describe(`device type
optional field
can take the values:desktop, mobile
default value: desktop`),
      os: z.string().default('windows').optional().describe(`device operating system
optional field
if you specify desktop in the device field, choose from the following values: windows, macos
default value: windows
if you specify mobile in the device field, choose from the following values: android, ios
default value: android`),
      block_depth: z.number().default(20).optional().describe(`parsing depth
optional field
number of blocks of results in SERP
max value: 700`)
    };
  }

  async handle(params:any): Promise<any> {
    try {
      console.error(JSON.stringify(params, null, 2));
      const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/organic/live/advanced`, 'POST', [{
        keyword: params.keyword,
        location_name: params.location_name,
        language_code: params.language_code,
        device: params.device,
        os: params.os,
        block_depth: params.block_depth,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 


```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOResponse } from '../../../../base.tool.js';

export class GoogleKeywordOverviewTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_google_keyword_overview';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      keywords: z.array(z.string()).describe(`keywords
required field
The maximum number of keywords you can specify: 700
The maximum number of characters for each keyword: 80
The maximum number of words for each keyword phrase: 10
the specified keywords will be converted to lowercase format, data will be provided in a separate array
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
you will not be charged for the keywords omitted in the results`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`),
      include_clickstream_data: z.boolean().optional().default(false).describe(
        `Include or exclude data from clickstream-based metrics in the result`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/keyword_overview/live', 'POST', [{
        keywords: params.keywords,
        location_name: params.location_name,
        language_code: params.language_code,
        include_clickstream_data: params.include_clickstream_data
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksBulkNewLostReferringDomainsTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_new_lost_referring_domains';
  }

  getDescription(): string {
    return `This endpoint will provide you with the number of referring domains pointing to the domains, subdomains and pages specified in the targets array.
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`;
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
required field
you can set up to 1000 domains, subdomains or webpages
the domain or subdomain should be specified without https:// and www.
the page should be specified with absolute URL (including http:// or https://)
example:
"targets": [
"forbes.com",
"cnn.com",
"bbc.com",
"yelp.com",
"https://www.apple.com/iphone/",
"https://ahrefs.com/blog/",
"ibm.com",
"https://variety.com/",
"https://stackoverflow.com/",
"www.trustpilot.com"
]`),
date_from: z.string().optional().describe(`starting date of the time range
optional field
this field indicates the date which will be used as a threshold for new and lost backlinks;
the backlinks that appeared in our index after the specified date will be considered as new;
the backlinks that weren’t found after the specified date, but were present before, will be considered as lost;
default value: today’s date -(minus) one month;
e.g. if today is 2021-10-13, default date_from will be 2021-09-13.
minimum value equals today’s date -(minus) one year;
e.g. if today is 2021-10-13, minimum date_from will be 2020-10-13.

date format: "yyyy-mm-dd"
example:
"2021-01-01"`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_new_lost_referring_domains/live', 'POST', [{
        targets: params.targets
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';

export class SerpYoutubeVideoCommentsLiveAdvancedTool extends BaseTool {
    constructor(dataForSEOClient: DataForSEOClient) {
        super(dataForSEOClient);
    }

    getName(): string {
        return 'serp_youtube_video_comments_live_advanced';
    }

    getDescription(): string {
        return 'provides data on the video comments you specify';
    }

    getParams(): z.ZodRawShape {
        return {
            video_id: z.string().describe("ID of the video"),
            location_name: z.string().describe(`full name of the location
required field
Location format - hierarchical, comma-separated (from most specific to least)
 Can be one of:
 1. Country only: "United States"
 2. Region,Country: "California,United States"
 3. City,Region,Country: "San Francisco,California,United States"`),
            language_code: z.string().describe("search engine language code (e.g., 'en')"),
            device: z.string().default('desktop').optional().describe(`device type
optional field
can take the values:desktop, mobile
default value: desktop`),
            os: z.string().default('windows').optional().describe(`device operating system
optional field
if you specify desktop in the device field, choose from the following values: windows, macos
default value: windows
if you specify mobile in the device field, choose from the following values: android, ios
default value: android`),
            depth: z.number().default(20).optional().describe(`parsing depth, number of results in SERP, max value: 700`)
        };
    }

    async handle(params: any): Promise<any> {
        try {
            console.error(JSON.stringify(params, null, 2));
            const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/video_comments/live/advanced`, 'POST', [{
                video_id: params.video_id,
                location_name: params.location_name,
                language_code: params.language_code,
                device: params.device,
                os: params.os,
                depth: params.depth,
            }]);
            return this.validateAndFormatResponse(response);
        } catch (error) {
            return this.formatErrorResponse(error);
        }
    }
}
```

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

```typescript
import { z } from 'zod';
import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { DataForSEOResponse } from '../../base.tool.js';
import { defaultGlobalToolConfig } from '../../../config/global.tool.js';

export class ContentParsingTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'on_page_content_parsing';
  }

  getDescription(): string {
    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.';
  }

  getParams(): z.ZodRawShape {
    return {
      url: z.string().describe("URL of the page to parse"),
      enable_javascript: z.boolean().optional().describe("Enable JavaScript rendering"),
      custom_js: z.string().optional().describe("Custom JavaScript code to execute"),
      custom_user_agent: z.string().optional().describe("Custom User-Agent header"),
      accept_language: z.string().optional().describe("Accept-Language header value"),
    };
  }

  async handle(params: { 
    url: string; 
    enable_javascript?: boolean; 
    custom_js?: string; 
    custom_user_agent?: string; 
    accept_language?: string; 
  }): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/on_page/content_parsing/live', 'POST', [{
        url: params.url,
        enable_javascript: params.enable_javascript,
        custom_js: params.custom_js,
        custom_user_agent: params.custom_user_agent,
        accept_language: params.accept_language,
        markdown_view: true
      }]);
      console.error(JSON.stringify(response));
      if(defaultGlobalToolConfig.fullResponse || this.supportOnlyFullResponse()){
        let data = response as DataForSEOFullResponse;
        this.validateResponseFull(data);
        let result = data.tasks[0].result;
        return this.formatResponse(result);
      }
      else{
        let data = response as DataForSEOResponse;
        this.validateResponse(data);
        let result = data.items[0].page_as_markdown;
        return this.formatResponse(result);
      }
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { DataForSEOClient } from '../client/dataforseo.client.js';
import { SerpApiModule } from '../modules/serp/serp-api.module.js';
import { AiOptimizationApiModule } from '../modules/ai-optimization/ai-optimization-api-module.js'
import { KeywordsDataApiModule } from '../modules/keywords-data/keywords-data-api.module.js';
import { OnPageApiModule } from '../modules/onpage/onpage-api.module.js';
import { DataForSEOLabsApi } from '../modules/dataforseo-labs/dataforseo-labs-api.module.js';
import { BacklinksApiModule } from '../modules/backlinks/backlinks-api.module.js';
import { BusinessDataApiModule } from '../modules/business-data-api/business-data-api.module.js';
import { DomainAnalyticsApiModule } from '../modules/domain-analytics/domain-analytics-api.module.js';
import { BaseModule } from '../modules/base.module.js';
import { EnabledModules, isModuleEnabled } from '../config/modules.config.js';
import { ContentAnalysisApiModule } from '../modules/content-analysis/content-analysis-api.module.js';

export class ModuleLoaderService {
  static loadModules(dataForSEOClient: DataForSEOClient, enabledModules: EnabledModules): BaseModule[] {
    const modules: BaseModule[] = [];

    if (isModuleEnabled('AI_OPTIMIZATION', enabledModules)) {
      modules.push(new AiOptimizationApiModule(dataForSEOClient));
    }
    if (isModuleEnabled('SERP', enabledModules)) {
      modules.push(new SerpApiModule(dataForSEOClient));
    }
    if (isModuleEnabled('KEYWORDS_DATA', enabledModules)) {
      modules.push(new KeywordsDataApiModule(dataForSEOClient));
    }
    if (isModuleEnabled('ONPAGE', enabledModules)) {
      modules.push(new OnPageApiModule(dataForSEOClient));
    }
    if (isModuleEnabled('DATAFORSEO_LABS', enabledModules)) {
      modules.push(new DataForSEOLabsApi(dataForSEOClient));
    }
    if (isModuleEnabled('BACKLINKS', enabledModules)) {
      modules.push(new BacklinksApiModule(dataForSEOClient));
    }
    if (isModuleEnabled('BUSINESS_DATA', enabledModules)) {
      modules.push(new BusinessDataApiModule(dataForSEOClient));
    }
    if (isModuleEnabled('DOMAIN_ANALYTICS', enabledModules)) {
      modules.push(new DomainAnalyticsApiModule(dataForSEOClient));
    }
    if(isModuleEnabled('CONTENT_ANALYSIS', enabledModules)) {
      modules.push(new ContentAnalysisApiModule(dataForSEOClient));
    }

    return modules;
  }
}
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOFullResponse, DataForSEOResponse } from '../../../../base.tool.js';
import { defaultGlobalToolConfig } from '../../../../../config/global.tool.js';

export class GoogleHistoricalSERP extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_google_historical_serp';
  }

  getDescription(): string {
    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.`;
  }

  getParams(): z.ZodRawShape {
    return {
      keyword: z.string().describe(`target keyword`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/historical_serps/live', 'POST', [{
        keyword: params.keyword,
        location_name: params.location_name,
        language_code: params.language_code
      }]);

      console.error(JSON.stringify(response));
      if(defaultGlobalToolConfig.fullResponse || this.supportOnlyFullResponse()){
        return this.validateAndFormatResponse(response);
      }
      else {
        let data = response as DataForSEOResponse;
        this.validateResponse(data);
        let result = data.items;
        let filteredResult = result.map(item => this.filterResponseFields(item, [     
          "datetime",
          "items.type",
          "items.title",
          "items.domain",
          "items.rank_absolute"]));
          console.error(JSON.stringify(filteredResult));
        return this.formatResponse(filteredResult);
      }
      } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';

export class SerpYoutubeVideoSubtitlesLiveAdvancedTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'serp_youtube_video_subtitles_live_advanced';
  }

  getDescription(): string {
    return 'provides data on the video subtitles you specify';
  }

  getParams(): z.ZodRawShape {
    return {
      video_id: z.string().describe("ID of the video"),
      location_name: z.string().describe(`full name of the location
required field
Location format - hierarchical, comma-separated (from most specific to least)
 Can be one of:
 1. Country only: "United States"
 2. Region,Country: "California,United States"
 3. City,Region,Country: "San Francisco,California,United States"`),
      language_code: z.string().describe("search engine language code (e.g., 'en')"),
      subtitles_language: z.string().optional().describe("language code of original text (e.g., 'en')"),
      subtitles_translate_language: z.string().optional().describe("language code of translated text (e.g., 'en')"),
      device: z.string().default('desktop').optional().describe(`device type
optional field
can take the values:desktop, mobile
default value: desktop`),
      os: z.string().default('windows').optional().describe(`device operating system
optional field
if you specify desktop in the device field, choose from the following values: windows, macos
default value: windows
if you specify mobile in the device field, choose from the following values: android, ios
default value: android`)
    };
  }

  async handle(params:any): Promise<any> {
    try {
      console.error(JSON.stringify(params, null, 2));
      const response = await this.dataForSEOClient.makeRequest(`/v3/serp/youtube/video_subtitles/live/advanced`, 'POST', [{
        video_id: params.video_id,
        location_name: params.location_name,
        language_code: params.language_code,
        subtitles_language: params.subtitles_language,
        subtitles_translate_language: params.subtitles_translate_language,
        device: params.device,
        os: params.os,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 



```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../../base.tool.js';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';

export class DataForSeoTrendsSubregionInterestsTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'keywords_data_dataforseo_trends_subregion_interests';
  }

  getDescription(): string {
    return `This endpoint will provide you with location-specific keyword popularity data from DataForSEO Trends`;
  }

  getParams(): z.ZodRawShape {
    return {
      location_name: z.string().nullable().default(null).describe(`full name of the location
        optional field
        in format "Country"
        example:
        United Kingdom`),
      keywords: z.array(z.string()).describe(`keywords
        the maximum number of keywords you can specify: 5`),
      type: z.enum(['web', 'news', 'ecommerce']).default('web').describe(`dataforseo trends type`),
      date_from: z.string().optional().describe(`starting date of the time range
          if you don’t specify this field, the current day and month of the preceding year will be used by default
          minimal value for the web type: 2004-01-01
          minimal value for other types: 2008-01-01
          date format: "yyyy-mm-dd"
          example:
          "2019-01-15"`),
      date_to: z.string().optional()
          .describe(
            `ending date of the time range
            if you don’t specify this field, the today’s date will be used by default
            date format: "yyyy-mm-dd"
            example:
            "2019-01-15"`),
      time_range: z.enum(['past_4_hours', 'past_day', 'past_7_days', 'past_30_days', 'past_90_days', 'past_12_months', 'past_5_years'])
          .default('past_7_days')
          .describe(
            `preset time ranges
            if you specify date_from or date_to parameters, this field will be ignored when setting a task`),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/dataforseo_trends/subregion_interests/live', 'POST', [{
        location_name: params.location_name,
        keywords: params.keywords,
        type: params.type,
        date_from: params.date_from,
        date_to: params.date_to,
        time_range: params.time_range,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksBulkNewLostBacklinksTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_bulk_new_lost_backlinks';
  }

  getDescription(): string {
    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.
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`;
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get rank for
required field
you can set up to 1000 domains, subdomains or webpages
the domain or subdomain should be specified without https:// and www.
the page should be specified with absolute URL (including http:// or https://)
example:
"targets": [
"forbes.com",
"cnn.com",
"bbc.com",
"yelp.com",
"https://www.apple.com/iphone/",
"https://ahrefs.com/blog/",
"ibm.com",
"https://variety.com/",
"https://stackoverflow.com/",
"www.trustpilot.com"
]`),
date_from: z.string().optional().describe(`starting date of the time range
optional field
this field indicates the date which will be used as a threshold for new and lost backlinks;
the backlinks that appeared in our index after the specified date will be considered as new;
the backlinks that weren’t found after the specified date, but were present before, will be considered as lost;
default value: today’s date -(minus) one month;
e.g. if today is 2021-10-13, default date_from will be 2021-09-13.
minimum value equals today’s date -(minus) one year;
e.g. if today is 2021-10-13, minimum date_from will be 2020-10-13.

date format: "yyyy-mm-dd"
example:
"2021-01-01"`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/bulk_new_lost_backlinks/live', 'POST', [{
        targets: params.targets
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../../base.tool.js';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';

export class DataForSeoTrendsDemographyTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'keywords_data_dataforseo_trends_demography';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      location_name: z.string().nullable().default(null).describe(`full name of the location
        optional field
        in format "Country"
        example:
        United Kingdom`),
      keywords: z.array(z.string()).describe(`keywords
        the maximum number of keywords you can specify: 5`),
      type: z.enum(['web', 'news', 'ecommerce']).default('web').describe(`dataforseo trends type`),
      date_from: z.string().optional().describe(`starting date of the time range
          if you don’t specify this field, the current day and month of the preceding year will be used by default
          minimal value for the web type: 2004-01-01
          minimal value for other types: 2008-01-01
          date format: "yyyy-mm-dd"
          example:
          "2019-01-15"`),
      date_to: z.string().optional()
          .describe(
            `ending date of the time range
            if you don’t specify this field, the today’s date will be used by default
            date format: "yyyy-mm-dd"
            example:
            "2019-01-15"`),
      time_range: z.enum(['past_4_hours', 'past_day', 'past_7_days', 'past_30_days', 'past_90_days', 'past_12_months', 'past_5_years'])
          .default('past_7_days')
          .describe(
            `preset time ranges
            if you specify date_from or date_to parameters, this field will be ignored when setting a task`),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/dataforseo_trends/demography/live', 'POST', [{
        location_name: params.location_name,
        keywords: params.keywords,
        type: params.type,
        date_from: params.date_from,
        date_to: params.date_to,
        time_range: params.time_range,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../../base.tool.js';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';

export class DataForSeoTrendsExploreTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'keywords_data_dataforseo_trends_explore';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      location_name: z.string().nullable().default(null).describe(`full name of the location
        optional field
        in format "Country"
        example:
        United Kingdom`),
      keywords: z.array(z.string()).describe(`keywords
        the maximum number of keywords you can specify: 5`),
      type: z.enum(['web', 'news', 'ecommerce']).default('web').describe(`dataforseo trends type`),
      date_from: z.string().optional().describe(`starting date of the time range
          if you don’t specify this field, the current day and month of the preceding year will be used by default
          minimal value for the web type: 2004-01-01
          minimal value for other types: 2008-01-01
          date format: "yyyy-mm-dd"
          example:
          "2019-01-15"`),
      date_to: z.string().optional()
          .describe(
            `ending date of the time range
            if you don’t specify this field, the today’s date will be used by default
            date format: "yyyy-mm-dd"
            example:
            "2019-01-15"`),
      time_range: z.enum(['past_4_hours', 'past_day', 'past_7_days', 'past_30_days', 'past_90_days', 'past_12_months', 'past_5_years'])
          .default('past_7_days')
          .describe(
            `preset time ranges
            if you specify date_from or date_to parameters, this field will be ignored when setting a task`),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/dataforseo_trends/explore/live', 'POST', [{
        location_name: params.location_name,
        keywords: params.keywords,
        type: params.type,
        date_from: params.date_from,
        date_to: params.date_to,
        time_range: params.time_range,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { BaseTool } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';

export class SerpOrganicLiveAdvancedTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'serp_organic_live_advanced';
  }

  getDescription(): string {
    return 'Get organic search results for a keyword in specified search engine';
  }

  getParams(): z.ZodRawShape {
    return {
      search_engine: z.string().default('google').describe("search engine name, one of: google, yahoo, bing."),
      location_name: z.string().default('United States').describe(`full name of the location
required field
Location format - hierarchical, comma-separated (from most specific to least)
 Can be one of:
 1. Country only: "United States"
 2. Region,Country: "California,United States"
 3. City,Region,Country: "San Francisco,California,United States"`),
      depth: z.number().min(10).max(700).default(10).describe(`parsing depth
optional field
number of results in SERP`),
      language_code: z.string().describe("search engine language code (e.g., 'en')"),
      keyword: z.string().describe("Search keyword"),
      max_crawl_pages: z.number().min(1).max(7).optional().default(1).describe(`page crawl limit
optional field
number of search results pages to crawl
max value: 100
Note: the max_crawl_pages and depth parameters complement each other`),
      device: z.string().default('desktop').optional().describe(`device type
optional field
can take the values:desktop, mobile
default value: desktop`),
      people_also_ask_click_depth: z.number().min(1).max(4).optional()
      .describe(`clicks on the corresponding element
        specify the click depth on the people_also_ask element to get additional people_also_ask_element items;`)
    };
  }

  async handle(params:any): Promise<any> {
    try {
      console.error(JSON.stringify(params, null, 2));
      const response = await this.dataForSEOClient.makeRequest(`/v3/serp/${params.search_engine}/organic/live/advanced`, 'POST', [{
        location_name: params.location_name,
        language_code: params.language_code,
        keyword: params.keyword,
        depth: params.depth,
        max_crawl_pages: params.max_crawl_pages,
        device: params.device,
        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,
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../base.tool.js';

export class WhoisOverviewTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'domain_analytics_whois_overview';
  }

  getDescription(): string {
    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`;
  }

  getParams(): z.ZodRawShape {
    return {
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned domains"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned businesses
optional field
default value: 0
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`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, <, <=, >, >=, =, <>, in, not_in, like, not_like, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["rating.value",">",3]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting parameter
example:
["rating.value,desc"]note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["rating.value,desc","rating.votes_count,desc"]`
      ),
      is_claimed: z.boolean().optional().describe(`indicates whether the business is verified by its owner on Google Maps`).default(true)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/domain_analytics/whois/overview/live', 'POST', [{        
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksTimeseriesSummaryTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_timeseries_summary';
  }

  getDescription(): string {
    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.
Data from this endpoint will be especially helpful for building time-series graphs of daily, weekly, monthly, and yearly link-building progress`;
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain to get data for
required field
a domain should be specified without https:// and www.
example:
"forbes.com"`),
      date_from: z.string().describe(`starting date of the time range
optional field
this field indicates the date which will be used as a threshold for summary data;
minimum value: 2019-01-30
maximum value shouldn’t exceed the date specified in the date_to
date format: "yyyy-mm-dd"
example:
"2021-01-01"`).optional(),
      date_to: z.string().describe(`ending date of the time range
optional field
if you don’t specify this field, the today’s date will be used by default
minimum value shouldn’t preceed the date specified in the date_from
maximum value: today’s date
date format: "yyyy-mm-dd"
example:
"2021-01-15"`).optional(),
        group_range: z.string().optional().describe(`time range which will be used to group the results
optional field
default value: month
possible values: day, week, month, year
note: for day, we will return items corresponding to all dates between and including date_from and date_to;
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

for example, if you specify:
"group_range": "month",
"date_from": "2022-03-23",
"date_to": "2022-05-13"
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

if there is no data for a certain day/week/month/year, we will return 0`).default('month')
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/timeseries_summary/live', 'POST', [{
        target: params.target,
        date_from: params.date_from,
        date_to: params.date_to,
        group_range: params.group_range
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import * as fs from 'fs';

export interface FieldConfiguration {
  supported_fields: Record<string, string[]>;
}

export class FieldConfigurationManager {
  private static instance: FieldConfigurationManager;
  private config: FieldConfiguration | null = null;

  private constructor() {}

  public static getInstance(): FieldConfigurationManager {
    if (!FieldConfigurationManager.instance) {
      FieldConfigurationManager.instance = new FieldConfigurationManager();
    }
    return FieldConfigurationManager.instance;
  }

  public loadFromFile(configPath: string): void {
    try {
      if (!fs.existsSync(configPath)) {
        console.warn(`Configuration file not found: ${configPath}`);
        return;
      }
      console.error(`Loading field configuration from: ${configPath}`);
      const configContent = fs.readFileSync(configPath, 'utf8');
      const parsedConfig = JSON.parse(configContent);

      // Validate the configuration structure
      if (!parsedConfig.supported_fields || typeof parsedConfig.supported_fields !== 'object') {
        throw new Error('Invalid configuration format. Expected { "supported_fields": { "tool_name": ["field1", "field2"] } }');
      }

      this.config = parsedConfig;
      console.log(`Field configuration loaded from: ${configPath}`);
    } catch (error) {
      console.error('Error loading field configuration:', error);
      throw error;
    }
  }

  public getFieldsForTool(toolName: string): string[] | null {
    if (!this.config) {
      return null; // No configuration loaded, return all fields
    }

    return this.config.supported_fields[toolName] || null;
  }

  public hasConfiguration(): boolean {
    return this.config !== null;
  }

  public isToolConfigured(toolName: string): boolean {
    return this.config !== null && toolName in this.config.supported_fields;
  }

  public getConfiguration(): FieldConfiguration | null {
    return this.config;
  }

  public clearConfiguration(): void {
    this.config = null;
  }
}

// Helper functions for easy access
export function getFieldsForTool(toolName: string): string[] | null {
  return FieldConfigurationManager.getInstance().getFieldsForTool(toolName);
}

export function isToolConfigured(toolName: string): boolean {
  return FieldConfigurationManager.getInstance().isToolConfigured(toolName);
}

export function hasFieldConfiguration(): boolean {
  return FieldConfigurationManager.getInstance().hasConfiguration();
}

export function loadFieldConfiguration(configPath: string): void {
  FieldConfigurationManager.getInstance().loadFromFile(configPath);
}

export function initializeFieldConfiguration(): void {
  const configPath = process.env.FIELD_CONFIG_PATH;
  
  if (configPath) {
    try {
      loadFieldConfiguration(configPath);
    } catch (error) {
      console.error('Failed to load field configuration:', error);
    }
  }
}
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksTimeseriesNewLostSummaryTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_timeseries_new_lost_summary';
  }

  getDescription(): string {
    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.
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.
Data from this endpoint will be especially helpful for building time-series graphs of new and lost backlinks and referring domains.

`;
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain to get data for
required field
a domain should be specified without https:// and www.
example:
"forbes.com"`),
      date_from: z.string().describe(`starting date of the time range
optional field
this field indicates the date which will be used as a threshold for summary data;
minimum value: 2019-01-30
maximum value shouldn’t exceed the date specified in the date_to
date format: "yyyy-mm-dd"
example:
"2021-01-01"`).optional(),
      date_to: z.string().describe(`ending date of the time range
optional field
if you don’t specify this field, the today’s date will be used by default
minimum value shouldn’t preceed the date specified in the date_from
maximum value: today’s date
date format: "yyyy-mm-dd"
example:
"2021-01-15"`).optional(),
        group_range: z.string().optional().describe(`time range which will be used to group the results
optional field
default value: month
possible values: day, week, month, year
note: for day, we will return items corresponding to all dates between and including date_from and date_to;
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

for example, if you specify:
"group_range": "month",
"date_from": "2022-03-23",
"date_to": "2022-05-13"
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

if there is no data for a certain day/week/month/year, we will return 0`).default('month')
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/timeseries_new_lost_summary/live', 'POST', [{
        target: params.target,
        date_from: params.date_from,
        date_to: params.date_to,
        group_range: params.group_range
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
type FieldPath = string | string[];

export function filterFields(data: any, fields: FieldPath[]): any {
  if (!data || !fields || fields.length === 0) {
    return data;
  }

  const result: any = {};

  fields.forEach(field => {
    const path = Array.isArray(field) ? field : field.split('.');
    extractAndSetValue(data, result, path);
  });

  return result;
}

function extractAndSetValue(source: any, target: any, path: string[]): void {
  if (path.length === 0) return;

  const [currentKey, ...remainingPath] = path;
  
  if (remainingPath.length === 0) {
    // This is the final key, extract the value
    if (currentKey === '*') {
      // Wildcard at the end - copy all properties/items
      if (Array.isArray(source)) {
        Object.assign(target, source);
      } else if (source && typeof source === 'object') {
        Object.assign(target, source);
      }
    } else if (source && typeof source === 'object' && currentKey in source) {
      target[currentKey] = source[currentKey];
    }
    return;
  }

  // Not the final key, need to go deeper
  if (currentKey === '*') {
    // Wildcard in the middle of the path
    if (Array.isArray(source)) {
      // Handle array with wildcard
      if (!Array.isArray(target)) {
        // Convert target to array to match source structure
        Object.keys(target).forEach(key => delete target[key]);
        Object.setPrototypeOf(target, Array.prototype);
        target.length = 0;
      }
      
      source.forEach((item, index) => {
        if (!target[index]) {
          target[index] = {};
        }
        extractAndSetValue(item, target[index], remainingPath);
      });
    } else if (source && typeof source === 'object') {
      // Handle object with wildcard
      Object.keys(source).forEach(key => {
        if (!target[key]) {
          target[key] = {};
        }
        extractAndSetValue(source[key], target[key], remainingPath);
      });
    }
  } else if (source && typeof source === 'object' && currentKey in source) {
    // Regular key handling
    const sourceValue = source[currentKey];
    
    if (Array.isArray(sourceValue)) {
      // Handle array - preserve array structure
      if (!target[currentKey]) {
        target[currentKey] = [];
      }
      
      sourceValue.forEach((item, index) => {
        if (!target[currentKey][index]) {
          target[currentKey][index] = {};
        }
        extractAndSetValue(item, target[currentKey][index], remainingPath);
      });
    } else if (sourceValue && typeof sourceValue === 'object') {
      // Handle object
      if (!target[currentKey]) {
        target[currentKey] = {};
      }
      extractAndSetValue(sourceValue, target[currentKey], remainingPath);
    }
  }
}

export function parseFieldPaths(fields: string[]): FieldPath[] {
  return fields.map(field => {
    // Handle array notation
    if (field.includes('[')) {
      const [base, index] = field.split('[');
      return [base, index.replace(']', '')];
    }
    return field;
  });
}

```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksAnchorTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_anchors';
  }

  getDescription(): string {
    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";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain, subdomain or webpage to get backlinks for
        required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned anchors"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned anchors
optional field
default value: 0
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`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
=, <>, in, not_in, like, not_like, ilike, not_ilike, regex, not_regex, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["rank",">","80"]
[["page_from_rank",">","55"],
"and",
["dofollow","=",true]]

[["first_seen",">","2017-10-23 11:31:45 +00:00"],
"and",
[["anchor","like","%seo%"],"or",["text_pre","like","%seo%"]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["rank,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["domain_from_rank,desc","page_from_rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/anchors/live', 'POST', [{
        target: params.target,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by)
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksDomainPagesTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_domain_pages';
  }

  getDescription(): string {
    return "This endpoint will provide you with a detailed overview of domain pages with backlink data for each page";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain, subdomain or webpage to get backlinks for
        required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned pages"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned pages
optional field
default value: 0
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`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["meta.internal_links_count",">","1"]
[["meta.external_links_count",">","2"],
"and",
["backlinks",">","10"]]

[["first_visited",">","2017-10-23 11:31:45 +00:00"],
"and",
[["title","like","%seo%"],"or",["referring_domains",">","10"]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["page_summary.backlinks,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["page_summary.backlinks,desc","page_summary.rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/domain_pages/live', 'POST', [{
        target: params.target,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksReferringDomainsTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);    
  }

  getName(): string {
    return 'backlinks_referring_domains';
  }

  getDescription(): string {
    return "This endpoint will provide you with a detailed overview of referring domains pointing to the target you specify";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain, subdomain or webpage to get backlinks for
        required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned pages"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned pages
optional field
default value: 0
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`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["meta.internal_links_count",">","1"]
[["meta.external_links_count",">","2"],
"and",
["backlinks",">","10"]]

[["first_visited",">","2017-10-23 11:31:45 +00:00"],
"and",
[["title","like","%seo%"],"or",["referring_domains",">","10"]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["page_summary.backlinks,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["page_summary.backlinks,desc","page_summary.rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/referring_domains/live', 'POST', [{
        target: params.target,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';

interface FilterField {
  type: string;
  path: string;
}

interface ToolFilters {
  [key: string]: {
    [field: string]: string;
  };
}

export class BusinessListingsFiltersTool extends BaseTool {
  private static cache: ToolFilters | null = null;
  private static lastFetchTime: number = 0;
  private static readonly CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

  // Map of tool names to their corresponding filter paths in the API response
  private static readonly TOOL_TO_FILTER_MAP: { [key: string]: string } = {
    'business_data_business_listings_search': 'search',
    'business_data_business_listings_categories_aggregation': 'categories_aggregation'
  };

  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'business_data_business_listings_filters';
  }

  getDescription(): string {
    return `Here you will find all the necessary information about filters that can be used with Business Data API business listings endpoints.

Please, keep in mind that filters are associated with a certain object in the result array, and should be specified accordingly.`;
  }

  protected supportOnlyFullResponse(): boolean {
    return true;
  }

  getParams(): z.ZodRawShape {
    return {
      tool: z.string().optional().describe('The name of the tool to get filters for')
    };
  }

  private async fetchAndCacheFilters(): Promise<ToolFilters> {
    const now = Date.now();
    
    // Return cached data if it's still valid
    if (BusinessListingsFiltersTool.cache && 
        (now - BusinessListingsFiltersTool.lastFetchTime) < BusinessListingsFiltersTool.CACHE_TTL) {
      return BusinessListingsFiltersTool.cache;
    }

    // Fetch fresh data
    const response = await this.client.makeRequest('/v3/business_data/business_listings/available_filters', 'GET', null, true) as DataForSEOFullResponse;
    this.validateResponseFull(response);

    // Transform the response into our cache format
    const filters: ToolFilters = {};
    const result = response.tasks[0].result[0];

    // Process each tool's filters
    for (const [toolName, filterPath] of Object.entries(BusinessListingsFiltersTool.TOOL_TO_FILTER_MAP)) {
      if (result && result[filterPath]) {
        filters[toolName] = result[filterPath];
      }
    }

    // Update cache
    BusinessListingsFiltersTool.cache = filters;
    BusinessListingsFiltersTool.lastFetchTime = now;
    return filters;
  }

  async handle(params: any): Promise<any> {
    try {
      const filters = await this.fetchAndCacheFilters();
      
      if (!params.tool) {
        return this.formatResponse(filters);
      }

      const toolFilters = filters[params.tool];
      if (!toolFilters) {
        throw new Error(`No filters found for tool: ${params.tool}`);
      }

      return this.formatResponse(toolFilters);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { PromptDefinition } from '../prompt-definition.js';


export const serpPrompts: PromptDefinition[] = [
  {
    name: 'analyze_local_seo_differences_in_the_top_10_google_results_for_two_target_markets',
    title: 'Analyze local SEO differences in the top 10 Google results for two target markets.',
    params: {
      keyword: z.string().describe('The keyword to analyze'),
      language: z.string().describe('The language of the search results'),
      location1: z.string().describe('The first location to analyze'),
      location2: z.string().describe('The second location to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              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.).`
            }
          }
        ]
      };
    }
  },
  {
    name: 'monitor_visibility_for_key_branded_searches_in_real_time',
    title: 'Monitor visibility for key branded searches in real-time.',
    params: {
      domain: z.string().describe('The domain to monitor'),
      location: z.string().describe('The location to monitor'),
      language: z.string().describe('The language to monitor'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              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.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'generate_domain_visibility_reports_and_track_ranking_changes',
    title: 'Generate domain visibility reports and track ranking changes.',
    params: {
      domain: z.string().describe('The domain to analyze'),
      location: z.string().describe('The location to analyze'),
      language: z.string().describe('The language to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              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.`
            }
          }
        ]
      };
    }
  },
]   

```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksDomainPagesSummaryTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);    
  }

  getName(): string {
    return 'backlinks_domain_pages_summary';
  }

  getDescription(): string {
    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";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain, subdomain or webpage to get backlinks for
        required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned anchors"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned anchors
optional field
default value: 0
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`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["referring_links_types.anchors",">","1"]
[["broken_pages",">","2"],
"and",
["backlinks",">","10"]]

[["first_seen",">","2017-10-23 11:31:45 +00:00"],
"and",
[["anchor","like","%seo%"],"or",["referring_domains",">","10"]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["backlinks,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["backlinks,desc","rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/domain_pages_summary/live', 'POST', [{
        target: params.target,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';

interface FilterField {
  type: string;
  path: string;
}

interface ToolFilters {
  [key: string]: {
    [field: string]: string;
  };
}

export class WhoisFiltersTool extends BaseTool {
  private static cache: ToolFilters | null = null;
  private static lastFetchTime: number = 0;
  private static readonly CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

  // Map of tool names to their corresponding filter paths in the API response
  private static readonly TOOL_TO_FILTER_MAP: { [key: string]: string } = {
    'domain_analytics_whois_overview': 'overview'
  };

  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'domain_analytics_whois_available_filters';
  }

  getDescription(): string {
    return `Here you will find all the necessary information about filters that can be used with DataForSEO WHOIS API endpoints.

Please, keep in mind that filters are associated with a certain object in the result array, and should be specified accordingly.`;
  }

  protected supportOnlyFullResponse(): boolean {
    return true;
  }

  getParams(): z.ZodRawShape {
    return {
      tool: z.string().optional().describe('The name of the tool to get filters for')
    };
  }

  private async fetchAndCacheFilters(): Promise<ToolFilters> {
    const now = Date.now();
    
    // Return cached data if it's still valid
    if (WhoisFiltersTool.cache && 
        (now - WhoisFiltersTool.lastFetchTime) < WhoisFiltersTool.CACHE_TTL) {
      return WhoisFiltersTool.cache;
    }

    // Fetch fresh data
    const response = await this.client.makeRequest('/v3/domain_analytics/whois/available_filters', 'GET', null, true) as DataForSEOFullResponse;
    this.validateResponseFull(response);

    // Transform the response into our cache format
    const filters: ToolFilters = {};
    const result = response.tasks[0].result[0];

    // Process each tool's filters
    for (const [toolName, filterPath] of Object.entries(WhoisFiltersTool.TOOL_TO_FILTER_MAP)) {
      const pathParts = filterPath.split('.');
      let current = result;
      
      // Navigate to the correct filter object
      for (const part of pathParts) {
        if (current && current[part]) {
          current = current[part];
        } else {
          current = null;
          break;
        }
      }

      if (current) {
        filters[toolName] = current;
      }
    }

    // Update cache
    WhoisFiltersTool.cache = filters;
    WhoisFiltersTool.lastFetchTime = now;

    return filters;
  }

  async handle(params: any): Promise<any> {
    try {
      const filters = await this.fetchAndCacheFilters();
      
      if (!params.tool) {
        return this.formatResponse(filters);
      }

      const toolFilters = filters[params.tool];
      if (!toolFilters) {
        throw new Error(`No filters found for tool: ${params.tool}`);
      }

      return this.formatResponse(toolFilters);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksReferringNetworksTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_referring_networks';
  }

  getDescription(): string {
    return "This endpoint will provide you with a detailed overview of referring domains pointing to the target you specify";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain, subdomain or webpage to get backlinks for
        required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      network_address_type: z.string().optional().default('ip').describe(`indicates the type of network to get data for
optional field
possible values: ip, subnet
default value: ip`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned networks"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned networks
optional field
default value: 0
if you specify the 10 value, the first ten domains in the results array will be omitted and the data will be provided for the successive pages`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["referring_pages",">","1"]
[["referring_pages",">","2"],
"and",
["backlinks",">","10"]]

[["first_seen",">","2017-10-23 11:31:45 +00:00"],
"and",
[["network_address","like","194.1.%"],"or",["referring_ips",">","10"]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["backlinks,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["backlinks,desc","rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/referring_networks/live', 'POST', [{
        target: params.target,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        network_address_type: params.network_address_type
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';

export class BacklinksTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_backlinks';
  }

  getDescription(): string {
    return "This endpoint will provide you with a list of backlinks and relevant data for the specified domain, subdomain, or webpage";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain, subdomain or webpage to get backlinks for
        required field
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      mode: z.string().default("as_is").describe(`results grouping type
optional field
possible grouping types:
as_is – returns all backlinks
one_per_domain – returns one backlink per domain
one_per_anchor – returns one backlink per anchor
default value: as_is`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned backlinks"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of the returned backlinks
optional field
default value: 0
if you specify the 10 value, the first ten backlinks in the results array will be omitted and the data will be provided for the successive backlinks`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
=, <>, in, not_in, like, not_like, ilike, not_ilike, regex, not_regex, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["rank",">","80"]
[["page_from_rank",">","55"],
"and",
["dofollow","=",true]]

[["first_seen",">","2017-10-23 11:31:45 +00:00"],
"and",
[["anchor","like","%seo%"],"or",["text_pre","like","%seo%"]]]`      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["rank,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["domain_from_rank,desc","page_from_rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/backlinks/live', 'POST', [{
        target: params.target,
        mode: params.mode,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by)
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/backlinks/tools/backlinks-page-intersection.tool.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';
import { mapArrayToNumberedKeys } from '../../../utils/map-array-to-numbered-keys.js';

export class BacklinksPageIntersectionTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_page_intersection';
  }

  getDescription(): string {
    return "This endpoint will provide you with the list of domains pointing to the specified websites. This endpoint is especially useful for creating a Link Gap feature that shows what domains link to your competitors but do not link out to your website";
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get links for
required field
you can set up to 20 domains, subdomains or webpages
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned results"),
      offset: z.number().min(0).optional().describe(
        `offset in the array of returned results
optional field
default value: 0
if you specify the 10 value, the first ten backlinks in the results array will be omitted and the data will be provided for the successive backlinks`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["1.rank",">","80"]
[["2.page_from_rank",">","55"],
"and",
["1.original","=","true"]]

[["1.first_seen",">","2017-10-23 11:31:45 +00:00"],
"and",
[["1.anchor","like","%seo%"],"or",["1.text_pre","not_like","%seo%"]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["rank,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["domain_from_rank,desc","page_from_rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/page_intersection/live', 'POST', [{
        targets: mapArrayToNumberedKeys(params.targets),
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool } from '../../base.tool.js';
import { mapArrayToNumberedKeys } from '../../../utils/map-array-to-numbered-keys.js';

export class BacklinksDomainIntersectionTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);    
  }

  getName(): string {
    return 'backlinks_domain_intersection';
  }

  getDescription(): string {
    return "This endpoint will provide you with the list of domains pointing to the specified websites. This endpoint is especially useful for creating a Link Gap feature that shows what domains link to your competitors but do not link out to your website";
  }

  getParams(): z.ZodRawShape {
    return {
      targets: z.array(z.string()).describe(`domains, subdomains or webpages to get links for
required field
you can set up to 20 domains, subdomains or webpages
a domain or a subdomain should be specified without https:// and www.
a page should be specified with absolute URL (including http:// or https://)`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned results"),
      offset: z.number().min(0).optional().describe(
        `offset in the array of returned results
optional field
default value: 0
if you specify the 10 value, the first ten backlinks in the results array will be omitted and the data will be provided for the successive backlinks`
      ),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["1.internal_links_count",">","1"]
[["2.referring_pages",">","2"],
"and",
["1.backlinks",">","10"]]

[["1.first_seen",">","2017-10-23 11:31:45 +00:00"],
"and",
[["2.target","like","%dataforseo.com%"],"or",["1.referring_domains",">","10"]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["backlinks,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["backlinks,desc","rank,asc"]`
      ),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/backlinks/domain_intersection/live', 'POST', [{
        targets: mapArrayToNumberedKeys(params.targets),
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOFullResponse } from '../../../base.tool.js';

interface FilterField {
  type: string;
  path: string;
}

interface ToolFilters {
  [key: string]: {
    [field: string]: string;
  };
}

export class DomainTechnologiesFiltersTool extends BaseTool {
  private static cache: ToolFilters | null = null;
  private static lastFetchTime: number = 0;
  private static readonly CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

  // Map of tool names to their corresponding filter paths in the API response
  private static readonly TOOL_TO_FILTER_MAP: { [key: string]: string } = {
    'domain_analytics_technologies_domains_by_technology': 'domains_by_technology',
    'domain_analytics_technologies_aggregation_technologies': 'aggregation_technologies',
    'domain_analytics_technologies_technologies_summary': 'technologies_summary',
    'domain_analytics_technologies_domains_by_html_terms': 'domains_by_html_terms'
  };

  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'domain_analytics_technologies_available_filters';
  }

  getDescription(): string {
    return `Here you will find all the necessary information about filters that can be used with DataForSEO Technologies API endpoints.

Please, keep in mind that filters are associated with a certain object in the result array, and should be specified accordingly.`;
  }

  protected supportOnlyFullResponse(): boolean {
    return true;
  }

  getParams(): z.ZodRawShape {
    return {
      tool: z.string().optional().describe('The name of the tool to get filters for')
    };
  }

  private async fetchAndCacheFilters(): Promise<ToolFilters> {
    const now = Date.now();
    
    // Return cached data if it's still valid
    if (DomainTechnologiesFiltersTool.cache && 
        (now - DomainTechnologiesFiltersTool.lastFetchTime) < DomainTechnologiesFiltersTool.CACHE_TTL) {
      return DomainTechnologiesFiltersTool.cache;
    }

    // Fetch fresh data
    const response = await this.client.makeRequest('/v3/domain_analytics/technologies/available_filters', 'GET', null, true) as DataForSEOFullResponse;
    this.validateResponseFull(response);

    // Transform the response into our cache format
    const filters: ToolFilters = {};
    const result = response.tasks[0].result[0];

    // Process each tool's filters
    for (const [toolName, filterPath] of Object.entries(DomainTechnologiesFiltersTool.TOOL_TO_FILTER_MAP)) {
      const pathParts = filterPath.split('.');
      let current = result;
      
      // Navigate to the correct filter object
      for (const part of pathParts) {
        if (current && current[part]) {
          current = current[part];
        } else {
          current = null;
          break;
        }
      }

      if (current) {
        filters[toolName] = current;
      }
    }

    // Update cache
    DomainTechnologiesFiltersTool.cache = filters;
    DomainTechnologiesFiltersTool.lastFetchTime = now;

    return filters;
  }

  async handle(params: any): Promise<any> {
    try {
      const filters = await this.fetchAndCacheFilters();
      
      if (!params.tool) {
        return this.formatResponse(filters);
      }

      const toolFilters = filters[params.tool];
      if (!toolFilters) {
        throw new Error(`No filters found for tool: ${params.tool}`);
      }

      return this.formatResponse(toolFilters);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { PromptDefinition } from '../prompt-definition.js';


export const backlinksPrompts: PromptDefinition[] = [
  {
    name: 'discover_your_strongest_backlinks_for_authority_building',
    title: 'Discover your strongest backlinks for authority building.',
    params: {
        domain: z.string().describe('The domain to find for'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Identify the top 10 highest-authority backlinks to '${params.domain}', grouped by referring domain. Include backlink type, anchor text, and target page.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'see_which_blog_content_earns_you_the_most_backlinks',
    title: 'See which blog content earns you the most backlinks.',
    params: {
      domain: z.string().describe('The domain to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content:{
              type: 'text',
              text: `Show me which blog posts on '${params.domain}' attract the most backlinks. List the top 5 by backlink count, and include title, referring domains, and anchor types.`
            }
          }
        ]
      }
    }
  },
  {
    name: 'find_new_link_opportunities_from_competitor_backlinks',
    title: 'Find new link opportunities from competitor backlinks.',
    params: {
      my_domain: z.string().describe('Your domain to compare against competitors'),
      competitor_1: z.string().describe('First competitor domain'),
      competitor_2: z.string().describe('Second competitor domain'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content:{
              type: 'text',
              text: `Which websites link to my competitors but not to '${params.my_domain}'? Use '${params.competitor_1}' and '${params.competitor_2}'. Return 15 domains I should target for outreach.`
            }
          }
        ]
      }
    }
  },
  {
    name: 'locate_broken_or_redirected_pages_that_waste_valuable_links',
    title: 'Locate broken or redirected pages that waste valuable links.',
    params: {
      domain: z.string().describe('The domain to analyze'),
      backlinks_count: z.number().default(30).describe('Minimum number of backlinks to consider a page valuable'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content:{
              type: 'text',
              text: `Find internal pages on '${params.domain}' that have over ${params.backlinks_count} backlinks but are 404 or redirected. Return URL, status code, backlink count, and top referring domains.`
            }
          }
        ]
      }
    }
  },
  {
    name: 'benchmark_backlink_gaps_between_you_and_a_competitor',
    title: 'Benchmark backlink gaps between you and a competitor.',
    params: {
      my_domain: z.string().describe('Your domain to compare against a competitor'),
      competitor: z.string().describe('Competitor domain to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content:{
              type: 'text',
              text: `Compare backlinks between '${params.my_domain}' and competitor '${params.competitor}'. Show 10 domains linking only to the competitor domain. Include domain authority and link count.`
            }
          }
        ]
      }
    }
  }
]

```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';
import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js';

interface FilterField {
  type: string;
  path: string;
}

interface ToolFilters {
  [key: string]: {
    [field: string]: string;
  };
}

export class BacklinksFiltersTool extends BaseTool {
  private static cache: ToolFilters | null = null;
  private static lastFetchTime: number = 0;
  private static readonly CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

  // Map of tool names to their corresponding filter paths in the API response
  private static readonly TOOL_TO_FILTER_MAP: { [key: string]: string } = {
    'backlinks_content_duplicates': 'content_duplicates',
    'backlinks_backlinks': 'backlinks',
    'backlinks_domain_pages': 'domain_pages',
    'backlinks_anchors': 'anchors',
    'backlinks_referring_domains': 'referring_domains',
    'backlinks_domain_intersection': 'domain_intersection',
    'backlinks_page_intersection': 'page_intersection',
    'backlinks_referring_networks': 'referring_networks',
    'backlinks_domain_pages_summary': 'domain_pages_summary',
    'backlinks_competitors': 'competitors'
  };

  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'backlinks_available_filters';
  }

  getDescription(): string {
    return `Here you will find all the necessary information about filters that can be used with DataForSEO Backlinks API endpoints.

Please, keep in mind that filters are associated with a certain object in the result array, and should be specified accordingly.`;
  }

  protected supportOnlyFullResponse(): boolean {
    return true;
  }

  getParams(): z.ZodRawShape {
    return {
      tool: z.string().optional().describe('The name of the tool to get filters for')
    };
  }

  private async fetchAndCacheFilters(): Promise<ToolFilters> {
    const now = Date.now();
    
    // Return cached data if it's still valid
    if (BacklinksFiltersTool.cache && 
        (now - BacklinksFiltersTool.lastFetchTime) < BacklinksFiltersTool.CACHE_TTL) {
      return BacklinksFiltersTool.cache;
    }

    // Fetch fresh data
    const response = await this.client.makeRequest('/v3/backlinks/available_filters', 'GET', null, true) as DataForSEOFullResponse;
    this.validateResponseFull(response);

    // Transform the response into our cache format
    const filters: ToolFilters = {};
    const result = response.tasks[0].result[0];

    // Process each tool's filters
    for (const [toolName, filterPath] of Object.entries(BacklinksFiltersTool.TOOL_TO_FILTER_MAP)) {
      const pathParts = filterPath.split('.');
      let current = result;
      
      // Navigate to the correct filter object
      for (const part of pathParts) {
        if (current && current[part]) {
          current = current[part];
        } else {
          current = null;
          break;
        }
      }

      if (current) {
        filters[toolName] = current;
      }
    }

    // Update cache
    BacklinksFiltersTool.cache = filters;
    BacklinksFiltersTool.lastFetchTime = now;

    return filters;
  }

  async handle(params: any): Promise<any> {
    try {
      const filters = await this.fetchAndCacheFilters();
      
      if (!params.tool) {
        return this.formatResponse(filters);
      }

      const toolFilters = filters[params.tool];
      if (!toolFilters) {
        throw new Error(`No filters found for tool: ${params.tool}`);
      }

      return this.formatResponse(toolFilters);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/content-analysis/tools/content-analysis-phrase-trends.ts:
--------------------------------------------------------------------------------

```typescript
import { any, z } from 'zod';
import { BaseTool } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';

export class ContentAnalysisPhraseTrendsTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'content_analysis_phrase_trends';
  }

  getDescription(): string {
    return `This endpoint will provide you with data on all citations of the target keyword for the indicated date range`;
  }

  getParams(): z.ZodRawShape {
    return {
      keyword: z.string().describe(`target keyword
        Note: to match an exact phrase instead of a stand-alone keyword, use double quotes and backslashes;`),
      keyword_fields: z.object({
        title: z.string().optional(),
        main_title: z.string().optional(),
        previous_title: z.string().optional(),
        snippet: z.string().optional()
      }).optional().describe(
        `target keyword fields and target keywords
        use this parameter to filter the dataset by keywords that certain fields should contain;
        you can indicate several fields;
        Note: to match an exact phrase instead of a stand-alone keyword, use double quotes and backslashes;
        example:
        {
          "snippet": "\\"logitech mouse\\"",
          "main_title": "sale"
        }`
      ),
      page_type: z.array(z.enum(['ecommerce','news','blogs', 'message-boards','organization'])).optional().describe(`target page types`),
      initial_dataset_filters: this.getFilterExpression().optional().describe(
        `initial dataset filtering parameters
        initial filtering parameters that apply to fields in the Search endpoint;
        you can add several filters at once (8 filters maximum);
        you should set a logical operator and, or between the conditions;
        the following operators are supported:
        regex, not_regex, <, <=, >, >=, =, <>, in, not_in, like,not_like, has, has_not, match, not_match
        you can use the % operator with like and not_like to match any string of zero or more characters;
        example:
        ["domain","<>", "logitech.com"]
        [["domain","<>","logitech.com"],"and",["content_info.connotation_types.negative",">",1000]]

        [["domain","<>","logitech.com"]],
        "and",
        [["content_info.connotation_types.negative",">",1000],
        "or",
        ["content_info.text_category","has",10994]]`
      ),
      date_from: z.string().describe(`starting date of the time range
        date format: "yyyy-mm-dd"`),
      date_to: z.string().describe(`ending date of the time range
        date format: "yyyy-mm-dd"`).optional(),
      date_group: z.enum(['day', 'week', 'month']).default('month').describe(`date grouping type`).optional(),
      internal_list_limit: z.number().min(1).max(20).default(1)
        .describe(
          `maximum number of elements within internal arrays
          you can use this field to limit the number of elements within the following arrays`)
        .optional(),
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/content_analysis/phrase_trends/live', 'POST', [{
        keyword: params.keyword,
        keyword_fields: params.keyword_fields,
        page_type: params.page_type,
        initial_dataset_filters: this.formatFilters(params.initial_dataset_filters),
        date_from: params.date_from,
        date_to: params.date_to,
        date_group: params.date_group,
        internal_list_limit: params.internal_list_limit
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/content-analysis/tools/content-analysis-search.tool.ts:
--------------------------------------------------------------------------------

```typescript
import { any, z } from 'zod';
import { BaseTool } from '../../base.tool.js';
import { DataForSEOClient } from '../../../client/dataforseo.client.js';

export class ContentAnalysisSearchTool extends BaseTool {
  constructor(dataForSEOClient: DataForSEOClient) {
    super(dataForSEOClient);
  }

  getName(): string {
    return 'content_analysis_search';
  }

  getDescription(): string {
    return `This endpoint will provide you with detailed citation data available for the target keyword`;
  }

  getParams(): z.ZodRawShape {
    return {
      keyword: z.string().describe(`target keyword
        Note: to match an exact phrase instead of a stand-alone keyword, use double quotes and backslashes;`),
      keyword_fields: z.object({
        title: z.string().optional(),
        main_title: z.string().optional(),
        previous_title: z.string().optional(),
        snippet: z.string().optional()
      }).optional().describe(
        `target keyword fields and target keywords
        use this parameter to filter the dataset by keywords that certain fields should contain;
        you can indicate several fields;
        Note: to match an exact phrase instead of a stand-alone keyword, use double quotes and backslashes;
        example:
        {
          "snippet": "\\"logitech mouse\\"",
          "main_title": "sale"
        }`
      ),
      page_type: z.array(z.enum(['ecommerce','news','blogs', 'message-boards','organization'])).optional().describe(`target page types`),
      search_mode: z.enum(['as_is', 'one_per_domain']).optional().describe(`results grouping type`),
      limit: z.number().min(1).max(1000).default(10).describe(`maximum number of results to return`),
      offset: z.number().min(0).default(0).describe(`offset in the results array of returned keywords`),
      filters: this.getFilterExpression().optional().describe(
        `array of results filtering parameters
optional field
you can add several filters at once (8 filters maximum)
you should set a logical operator and, or between the conditions
the following operators are supported:
regex, not_regex, <, <=, >, >=, =, <>, in, not_in, like,not_like, match, not_match
you can use the % operator with like and not_like to match any string of zero or more characters
example:
["country","=", "US"]
[["domain_rank",">",800],"and",["content_info.connotation_types.negative",">",0.9]]

[["domain_rank",">",800],
"and",
[["page_types","has","ecommerce"],
"or",
["content_info.text_category","has",10994]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["content_info.sentiment_connotations.anger,desc"]
default rule:
["content_info.sentiment_connotations.anger,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["content_info.sentiment_connotations.anger,desc","keyword_data.keyword_info.cpc,desc"]`,
      ),    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.dataForSEOClient.makeRequest('/v3/content_analysis/search/live', 'POST', [{
        keyword: params.keyword,
        page_type: params.page_type,
        search_mode: params.search_mode,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/market-analysis/google-top-searches.tool.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../../base.tool.js';

export class GoogleTopSearchesTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_google_top_searches';
  }

  getDescription(): string {
    return `The Top Searches endpoint of DataForSEO Labs API can provide you with over 7 billion keywords from the DataForSEO Keyword Database. Each keyword in the API response is provided with a set of relevant keyword data with Google Ads metrics`;
  }

  getParams(): z.ZodRawShape {
    return {
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned keywords
        optional field
        default value: 0
        if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords`
      ),
      filters: this.getFilterExpression().optional().describe(
        `you can add several filters at once (8 filters maximum)
        you should set a logical operator and, or between the conditions
        the following operators are supported:
        regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like
        you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters
        merge operator must be a string and connect two other arrays, availible values: or, and.
        example:
     ["keyword_info.search_volume",">",0]
[["keyword_info.search_volume","in",[0,1000]],
"and",
["keyword_info.competition_level","=","LOW"]][["keyword_info.search_volume",">",100],
"and",
[["keyword_info.cpc","<",0.5],
"or",
["keyword_info.high_top_of_page_bid","<=",0.5]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `resuresults sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting type
example:
["keyword_info.competition,desc"]
default rule:
["keyword_info.search_volume,desc"]
note that you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["keyword_info.search_volume,desc","keyword_info.cpc,desc"]`
      ),
      include_clickstream_data: z.boolean().optional().default(false).describe(
        `Include or exclude data from clickstream-based metrics in the result`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/top_searches/live', 'POST', [{
        location_name: params.location_name,
        language_code: params.language_code,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        include_clickstream_data: params.include_clickstream_data
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/keyword-research/google-keywords-for-site.tool.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../../../../../client/dataforseo.client.js';
import { BaseTool } from '../../../../base.tool.js';

export class GoogleKeywordsForSiteTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

  getName(): string {
    return 'dataforseo_labs_google_keywords_for_site';
  }

  getDescription(): string {
    return `The Keywords For Site endpoint will provide you with a list of keywords relevant to the target domain. Each keyword is supplied with relevant, search volume data for the last month, cost-per-click, competition`;
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`target domain`),
      location_name: z.string().default("United States").describe(`full name of the location
required field
only in format "Country" (not "City" or "Region")
example:
'United Kingdom', 'United States', 'Canada'`),
      language_code: z.string().default("en").describe(
        `language code
        required field
        example:
        en`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"),
      offset: z.number().min(0).optional().describe(
        `offset in the results array of returned keywords
        optional field
        default value: 0
        if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords`
      ),
      filters: this.getFilterExpression().optional().describe(
        `you can add several filters at once (8 filters maximum)
        you should set a logical operator and, or between the conditions
        the following operators are supported:
        regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like
        you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters
        merge operator must be a string and connect two other arrays, availible values: or, and.
        example:
      ["keyword_info.search_volume",">",0]
[["keyword_info.search_volume","in",[0,1000]],
"and",
["keyword_info.competition_level","=","LOW"]][["keyword_info.search_volume",">",100],
"and",
[["keyword_info.cpc","<",0.5],
"or",
["keyword_info.high_top_of_page_bid","<=",0.5]]]`
      ),
      order_by: z.array(z.string()).optional().describe(
        `results sorting rules
optional field
you can use the same values as in the filters array to sort the results
possible sorting types:
asc – results will be sorted in the ascending order
desc – results will be sorted in the descending order
you should use a comma to set up a sorting parameter
default rule:
["relevance,desc"]
example:
["relevance,desc","keyword_info.search_volume,desc"]`,
      ),
      include_subdomains: z.boolean().optional().describe("Include keywords from subdomains"),
      include_clickstream_data: z.boolean().optional().default(false).describe(
        `Include or exclude data from clickstream-based metrics in the result`)
    };
  }

  async handle(params: any): Promise<any> {
    try {
      const response = await this.client.makeRequest('/v3/dataforseo_labs/google/keywords_for_site/live', 'POST', [{
        target: params.target,
        location_name: params.location_name,
        language_code: params.language_code,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        include_subdomains: params.include_subdomains,
        include_clickstream_data: params.include_clickstream_data
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/worker/index-worker.ts:
--------------------------------------------------------------------------------

```typescript
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from 'zod';
import { DataForSEOClient, DataForSEOConfig } from '../core/client/dataforseo.client.js';
import { EnabledModulesSchema } from '../core/config/modules.config.js';
import { BaseModule, ToolDefinition } from '../core/modules/base.module.js';
import { ModuleLoaderService } from '../core/utils/module-loader.js';
import { version, name } from './version.worker.js';

/**
 * DataForSEO MCP Server for Cloudflare Workers
 * 
 * This server provides MCP (Model Context Protocol) access to DataForSEO APIs
 * through a Cloudflare Worker runtime using the agents/mcp pattern.
 */

// Server metadata
const SERVER_NAME = `${name} (Worker)`;
const SERVER_VERSION = version;
globalThis.__PACKAGE_VERSION__ = version;
globalThis.__PACKAGE_NAME__ = name;
/**
 * DataForSEO MCP Agent for Cloudflare Workers
 */
export class DataForSEOMcpAgent extends McpAgent {
  server = new McpServer({
    name: SERVER_NAME,
    version: SERVER_VERSION,
  });

  constructor(ctx: DurableObjectState, protected env: Env){
    super(ctx, env);
  }

  async init() {
    const workerEnv = this.env || (globalThis as any).workerEnv;
    if (!workerEnv) {
      throw new Error(`Worker environment not available`);
    }

    // Initialize DataForSEO client
    const dataForSEOConfig: DataForSEOConfig = {
      username: workerEnv.DATAFORSEO_USERNAME || "",
      password: workerEnv.DATAFORSEO_PASSWORD || "",
    };
    
    const dataForSEOClient = new DataForSEOClient(dataForSEOConfig);
    
    // Parse enabled modules from environment
    const enabledModules = EnabledModulesSchema.parse(workerEnv.ENABLED_MODULES);
    
    // Initialize and load modules
    const modules: BaseModule[] = ModuleLoaderService.loadModules(dataForSEOClient, enabledModules);
    
    // Register tools from all modules
    modules.forEach(module => {
      const tools = module.getTools();
      Object.entries(tools).forEach(([name, tool]) => {
        const typedTool = tool as ToolDefinition;
        const schema = z.object(typedTool.params);
        this.server.tool(
          name,
          schema.shape,
          typedTool.handler
        );
      });
    });
  }
}

/**
 * Creates a JSON-RPC error response
 */
function createErrorResponse(code: number, message: string): Response {
  return new Response(JSON.stringify({
    jsonrpc: "2.0",
    error: { code, message },
    id: null
  }), {
    status: code === -32001 ? 401 : 400,
    headers: { 'Content-Type': 'application/json' }
  });
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    // Store environment in global context for McpAgent access
    (globalThis as any).workerEnv = env;

    // Health check endpoint
    if (url.pathname === '/health' && request.method === 'GET') {
      return new Response(JSON.stringify({
        status: 'healthy',
        server: SERVER_NAME,
        version: SERVER_VERSION,
        timestamp: new Date().toISOString()
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
    // Check if credentials are configured
    if (!env.DATAFORSEO_USERNAME || !env.DATAFORSEO_PASSWORD) {
      if (['/mcp','/http', '/sse', '/messages','/sse/message'].includes(url.pathname)) {
        return createErrorResponse(-32001, "DataForSEO credentials not configured in worker environment variables");
      }
    }
    // MCP endpoints using McpAgent pattern
    if (url.pathname === "/sse" || url.pathname === "/sse/message") {
      return DataForSEOMcpAgent.serveSSE("/sse").fetch(request, env, ctx);
    }

    if (url.pathname === "/mcp" || url.pathname == '/http') {
      return DataForSEOMcpAgent.serve("/mcp").fetch(request, env, ctx);
    }

    return new Response("Not found", { status: 404 });
  },
};
```
Page 1/3FirstPrevNextLast