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

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

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

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

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

  getDescription(): string {
    return "This endpoint will provide you with a list of competitors that share some part of the backlink profile with a target website, along with a number of backlink intersections and the rank of every competing website";
  }

  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 domains"),
      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:
["rank",">","100"]
[["target","like","%forbes%"],
"and",
[["rank",">","100"],"or",["intersections",">","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 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:
["intersections,desc","rank,asc"]`
      ),
      main_domain: z.boolean().optional().describe(`indicates if only main domain of the target will be included in the search
if set to true, only the main domain will be included in search`).default(true),
      exclude_large_domains: z.boolean().optional().describe(`indicates whether large domain will appear in results
if set to true, the results from the large domain (google.com, amazon.com, etc.) will be omitted`).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/competitors/live', 'POST', [{
        target: params.target,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        main_domain: params.main_domain,
        exclude_large_domains: params.exclude_large_domains,
        exclude_internal_backlinks: params.exclude_internal_backlinks
      }]);
      return this.validateAndFormatResponse(response);

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

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

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

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

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

  getDescription(): string {
    return 'This endpoint will provide you with the keyword popularity data from the ‘Explore’ feature of Google Trends. You can check keyword trends for Google Search, Google News, Google Images, Google Shopping, and YouTube';
  }

  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(`keywords
        the maximum number of keywords you can specify: 5
        the maximum number of characters you can specify in a keyword: 100
        the minimum number of characters must be greater than 1
        comma characters (,) in the specified keywords will be unset and ignored
        Note: keywords cannot consist of a combination of the following characters: < > | \ " - + = ~ ! : * ( ) [ ] { }

        Note: to obtain google_trends_topics_list and google_trends_queries_list items, specify no more than 1 keyword`),
      type: z.enum(['web', 'news', 'youtube','images','froogle']).default('web').describe(`google 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_hour', '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`),
      item_types: z.array(z.enum(['google_trends_graph', 'google_trends_map', 'google_trends_topics_list', 'google_trends_queries_list']))
          .default(['google_trends_graph'])
          .describe(
            `types of items returned
            to speed up the execution of the request, specify one item at a time`),
      category_code: z.nullable(z.number()).default(null)
          .describe(
            `google trends search category
            you can receive the list of available categories with their category_code by making a separate request to the keywords_data_google_trends_categories tool`)
    };
  }

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

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

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


export const onpagePrompts: PromptDefinition[] = [
  {
    name: 'identify_technical_performance_issues_affecting_crawlability_and_ranking',
    title: 'Identify technical performance issues affecting crawlability and ranking',
    params: {
      url: z.string().describe('The URL of the page to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Audit page '${params.url}' for crawlability issues, including robots.txt restrictions, noindex tags, and broken internal links. Highlight what's preventing Google from indexing or ranking this page.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'detect_missing_or_duplicate_meta_tags_hurting_seo',
    title: 'Detect missing or duplicate meta tags hurting SEO',
    params: {
      url: z.string().describe('The URL of the page to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Review page '${params.url}' to check for missing or duplicate meta title and meta description tags, use validate_micromarkup, enable_javascript, and enable_browser_rendering. Let me know if the tags are too long, too short, or missing, and how to fix them.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'check_for_slow_load_time_and_mobile_compatibility_issues',
    title: 'Check for slow load time and mobile compatibility issues',
    params: {
      url: z.string().describe('The URL of the page to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Analyze page '${params.url}' for speed and mobile usability. Use load_resources, enable_javascript, and enable_browser_rendering. Tell me what's slowing it down or making it hard to use on mobile, include the measurements, and give practical steps to improve performance.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'evaluate_internal_linking_and_crawl_depth_for_better_indexing',
    title: 'Evaluate internal linking and crawl depth for better indexing',
    params: {
      url: z.string().describe('The URL of the page to analyze'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Check how well '${params.url}' is connected internally. Use load_resources, enable_javascript, and enable_browser_rendering. Tell me if it's buried too deep in the site structure or lacks internal links that could help search engines find and rank it. Include the data for each issue or metric.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'analyze_keyword_optimization_and_content_gaps',
    title: 'Analyze keyword optimization and content gaps',
    params: {
      url: z.string().describe('The URL of the page to analyze'),
      keyword: z.string().describe('The primary keyword to optimize for'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Evaluate '${params.url}' for how well it's optimized for the keyword '${params.keyword}'. Analyze on-page SEO elements like title, meta description, headings (H1-H6), internal links, and keyword usage, extract and parse all content elements (headings, paragraphs, alt attributes, etc.), and check for keyword placement and semantic relevance. Identify missing keyword placements and content gaps that could affect its relevance and ranking.`
            }
          }
        ]
      };
    }
  },
]
```

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

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

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

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

  getDescription(): string {
    return "This endpoint will provide you with a list of domains ranking for the keywords you specify. You will also get SERP rankings, rating, estimated traffic volume, and visibility values the provided domains gain from the specified keywords.";
  }

  getParams(): z.ZodRawShape {
    return {
      keywords: z.array(z.string()).describe(`keywords array
required field
the results will be based on the keywords you specify in this array
UTF-8 encoding;
the keywords will be converted to lowercase format;
you can specify the maximum of 200 keywords`),
      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:
        ["ranked_serp_element.serp_item.rank_group","<=",10]
        [["ranked_serp_element.serp_item.rank_group","<=",10],"or",["ranked_serp_element.serp_item.type","<>","paid"]]
        [["keyword_data.keyword_info.search_volume","<>",0],"and",[["ranked_serp_element.serp_item.type","<>","paid"],"or",["ranked_serp_element.serp_item.is_malicious","=",false]]]`
      ),
      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
the comma is used as a separator
example:
["avg_position,asc"]
default rule:
["rating,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:
["avg_position,asc","etv,desc"]`
      ),
      include_subdomains: z.boolean().optional().describe("Include keywords from subdomains")
    };
  }

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

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

```typescript
import { DataForSEOClient } from '../../client/dataforseo.client.js';
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { backlinksPrompts } from './backlinks.prompt.js';
import { BacklinksAnchorTool } from './tools/backlinks-anchor.tool.js';
import { BacklinksTool } from './tools/backlinks-backlinks.tool.js';
import { BacklinksBulkBacklinksTool } from './tools/backlinks-bulk-backlinks.tool.js';
import { BacklinksBulkNewLostBacklinksTool } from './tools/backlinks-bulk-new-lost-backlinks.tool.js';
import { BacklinksBulkNewLostReferringDomainsTool } from './tools/backlinks-bulk-new-lost-referring-domains.tool.js';
import { BacklinksBulkPagesSummaryTool } from './tools/backlinks-bulk-pages-summary.js';
import { BacklinksBulkRanksTool } from './tools/backlinks-bulk-ranks.tool.js';
import { BacklinksBulkReferringDomainsTool } from './tools/backlinks-bulk-referring-domains.tool.js';
import { BacklinksBulkSpamScoreTool } from './tools/backlinks-bulk-spam-score.tool.js';
import { BacklinksCompetitorsTool } from './tools/backlinks-competitors.tool.js';
import { BacklinksDomainIntersectionTool } from './tools/backlinks-domain-intersection.tool.js';
import { BacklinksDomainPagesSummaryTool } from './tools/backlinks-domain-pages-summary.tool.js';
import { BacklinksDomainPagesTool } from './tools/backlinks-domain-pages.tool.js';
import { BacklinksFiltersTool } from './tools/backlinks-filters.tool.js';
import { BacklinksPageIntersectionTool } from './tools/backlinks-page-intersection.tool.js';
import { BacklinksReferringDomainsTool } from './tools/backlinks-referring-domains.tool.js';
import { BacklinksReferringNetworksTool } from './tools/backlinks-referring-networks.tool.js';
import { BacklinksSummaryTool } from './tools/backlinks-summary.tool.js';
import { BacklinksTimeseriesNewLostSummaryTool } from './tools/backlinks-timeseries-new-lost-summary.tool.js';
import { BacklinksTimeseriesSummaryTool } from './tools/backlinks-timeseries-summary.tool.js';

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

  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new BacklinksTool(this.dataForSEOClient),
      new BacklinksAnchorTool(this.dataForSEOClient),
      new BacklinksBulkBacklinksTool(this.dataForSEOClient),
      new BacklinksBulkNewLostReferringDomainsTool(this.dataForSEOClient),
      new BacklinksBulkNewLostBacklinksTool(this.dataForSEOClient),
      new BacklinksBulkRanksTool(this.dataForSEOClient),
      new BacklinksBulkReferringDomainsTool(this.dataForSEOClient),
      new BacklinksBulkSpamScoreTool(this.dataForSEOClient),
      new BacklinksCompetitorsTool(this.dataForSEOClient),
      new BacklinksDomainIntersectionTool(this.dataForSEOClient),
      new BacklinksDomainPagesSummaryTool(this.dataForSEOClient),
      new BacklinksDomainPagesTool(this.dataForSEOClient),
      new BacklinksPageIntersectionTool(this.dataForSEOClient),
      new BacklinksReferringDomainsTool(this.dataForSEOClient),
      new BacklinksReferringNetworksTool(this.dataForSEOClient),
      new BacklinksSummaryTool(this.dataForSEOClient),
      new BacklinksTimeseriesNewLostSummaryTool(this.dataForSEOClient),
      new BacklinksTimeseriesSummaryTool(this.dataForSEOClient),
      new BacklinksBulkPagesSummaryTool(this.dataForSEOClient),
      new BacklinksFiltersTool(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 backlinksPrompts.reduce((acc, prompt) => ({
        ...acc,
        [prompt.name]: {
          description: prompt.description,
          params: prompt.params,
          handler: (params: any) => {

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

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

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

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

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

  getDescription(): string {
    return `Business Listings Search API provides results containing information about business entities listed on Google Maps in the specified categories. You will receive the address, contacts, rating, working hours, and other relevant data`;
  }

  getParams(): z.ZodRawShape {
    return {
      description: z.string().optional().describe(`description of the element in SERP
optional field
the description of the business entity for which the results are collected;
can contain up to 200 characters`),
      title: z.string().optional().describe(`title of the element in SERP
optional field
the name of the business entity for which the results are collected;
can contain up to 200 characters`),
      categories: z.array(z.string()).optional().describe(`business categories
the categories you specify are used to search for business listings;
if you don’t use this field, we will return business listings found in the specified location;
you can specify up to 10 categories`),
      location_coordinate: z.string().optional().describe(`GPS coordinates of a location
optional field
location_coordinate parameter should be specified in the “latitude,longitude,radius” format
the maximum number of decimal digits for “latitude” and “longitude”: 7
the value of “radius” is specified in kilometres (km)
the minimum value for “radius”: 1
the maximum value for “radius”: 100000
example:
53.476225,-2.243572,200`),
      limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned businesses"),
      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/business_data/business_listings/search/live', 'POST', [{
        description: params.description,
        title: params.title,
        categories: params.categories,
        limit: params.limit,
        offset: params.offset,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        location_coordinate: params.location_coordinate
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

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

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

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

  getDescription(): string {
    return `This endpoint will provide you with a full overview of ranking and traffic data of the competitor domains from organic and paid search. In addition to that, you will get the metrics specific to the keywords both competitor domains and your domain rank for within the same SERP.`;
  }

  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`),
      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:
        ["metrics.organic.count",">",50]
        [["metrics.organic.pos_1","<>",0],"and",["metrics.organic.impressions_etv",">=","10"]]

        [[["metrics.organic.count",">=",50],"and",["metrics.organic.pos_1","in",[1,5]]],
        "or",
        ["metrics.organic.etv",">=","100"]]`
          ),
      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"]`
          ),
      exclude_top_domains: z.boolean().default(true).describe(`indicates whether to exclude world's largest websites
        optional field
        default value: false
        set to true if you want to get highly-relevant competitors excluding the top websites`),
      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/competitors_domain/live', 'POST', [{
        target: params.target,
        location_name: params.location_name,
        language_code: params.language_code,
        ignore_synonyms: params.ignore_synonyms,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        exclude_top_domains: params.exclude_top_domains,
        item_types: ['organic'],
        include_clickstream_data: params.include_clickstream_data
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/content-analysis/tools/content-analysis-summary.ts:
--------------------------------------------------------------------------------

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

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

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

  getDescription(): string {
    return `This endpoint will provide you with an overview of 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`),
      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]]`
      ),
      positive_connotation_threshold: z.number()
        .describe(`positive connotation threshold
          specified as the probability index threshold for positive sentiment related to the citation content
          if you specify this field, connotation_types object in the response will only contain data on citations with positive sentiment probability more than or equal to the specified value`).min(0).max(1).optional().default(0.4),
      sentiments_connotation_threshold: z.number()
        .describe(`sentiment connotation threshold
specified as the probability index threshold for sentiment connotations related to the citation content
if you specify this field, sentiment_connotations object in the response will only contain data on citations where the
probability per each sentiment is more than or equal to the specified value`)
        .min(0).max(1).optional().default(0.4),
      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/summary/live', 'POST', [{
        keyword: params.keyword,
        keyword_fields: params.keyword_fields,
        page_type: params.page_type,
        initial_dataset_filters: this.formatFilters(params.initial_dataset_filters),
        positive_connotation_threshold: params.positive_connotation_threshold,
        sentiments_connotation_threshold: params.sentiments_connotation_threshold,
        internal_list_limit: params.internal_list_limit
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

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

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

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

  getDescription(): string {
    return `This endpoint will provide you with a list of subdomains of the specified domain, along with the ranking distribution across organic and paid search. In addition to that, you will also get the estimated traffic volume of subdomains based on search volume.`;
  }

  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`),
      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:
        ["metrics.organic.count",">",50]
        [["metrics.organic.pos_1","<>",0],"and",["metrics.organic.impressions_etv",">=","10"]]
        [[["metrics.organic.count",">=",50],"and",["metrics.organic.pos_1","in",[1,5]]],"or",["metrics.organic.etv",">=","100"]]`
      ),
      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 specify a sorting type
example:
["metrics.paid.etv,asc"]
Note: you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["metrics.organic.etv,desc","metrics.paid.count,asc"]
default rule:
["metrics.organic.count,desc"]`
      ),
      item_types: z.array(z.string()).optional().describe(
        `item types to return
        optional field
        default: ['organic']
        possible values:
        organic
        paid`
      ),
      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/subdomains/live', 'POST', [{
        target: params.target,
        location_name: params.location_name,
        language_code: params.language_code,
        ignore_synonyms: params.ignore_synonyms,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        exclude_top_domains: params.exclude_top_domains,
        item_types: params.item_types,
        include_clickstream_data: params.include_clickstream_data,
        limit: params.limit,
        offset: params.offset
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/competitor-research/google-ranked-keywords.tool.ts:
--------------------------------------------------------------------------------

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

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

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

  getDescription(): string {
    return "This endpoint will provide you with the list of keywords that any domain or webpage is ranking for. You will also get SERP elements related to the keyword position, as well as impressions, monthly searches and other data relevant to the returned keywords.";
  }

  getParams(): z.ZodRawShape {
    return {
      target: z.string().describe(`domain name or page url
required field
the domain name of the target website or URL of the target webpage;
the domain name must be specified without https:// or www.;
the webpage URL must be specified with https:// or www.
Note: if you specify the webpage URL without https:// or www., the result will be returned for the entire domain rather than the specific page
`),
      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(
        `Array of filter conditions and logical operators. Each filter condition is an array of [field, operator, value].
        Maximum 8 filters allowed.
        Available operators: =, <>, <, <=, >, >=, in, not_in, like, not_like, ilike, not_ilike, regex, not_regex, match, not_match
        Logical operators: "and", "or"
        Examples:
        Simple filter: [["ranked_serp_element.serp_item.rank_group","<=",10]]
        With logical operator: [["ranked_serp_element.serp_item.rank_group","<=",10],"or",["ranked_serp_element.serp_item.type","<>","paid"]]
        Complex filter: [["keyword_data.keyword_info.search_volume","<>",0],"and",[["ranked_serp_element.serp_item.type","<>","paid"],"or",["ranked_serp_element.serp_item.is_malicious","=",false]]]`
      ),
      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:
["keyword_data.keyword_info.competition,desc"]
default rule:
["ranked_serp_element.serp_item.rank_group,asc"]
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_data.keyword_info.search_volume,desc","keyword_data.keyword_info.cpc,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/ranked_keywords/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/core/modules/dataforseo-labs/tools/google/keyword-research/google-keywords-suggestions.tool.ts:
--------------------------------------------------------------------------------

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

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

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

  getDescription(): string {
    return `The Keyword Suggestions provides search queries that include the specified seed keyword.

The algorithm is based on the full-text search for the specified keyword and therefore returns only those search terms that contain the keyword you set in the POST array with additional words before, after, or within the specified key phrase. Returned keyword suggestions can contain the words from the specified key phrase in a sequence different from the one you specify.

As a result, you will get a list of long-tail keywords with each keyword in the list matching the specified search term.

Along with each suggested keyword, you will get its search volume rate for the last month, search volume trend for the previous 12 months, as well as current cost-per-click and competition values. Moreover, this endpoint supplies minimum, maximum and average values of daily impressions, clicks and CPC for each result.

`;
  }

  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`),
      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
a comma is used as a separator
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/keyword_suggestions/live', 'POST', [{
        keyword: params.keyword,
        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-related-keywords.tool.ts:
--------------------------------------------------------------------------------

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

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

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

  getDescription(): string {
    return `The Related Keywords endpoint provides keywords appearing in the
 "searches related to" SERP element
You can get up to 4680 keyword ideas by specifying the search depth. Each related keyword comes with the list of relevant product categories, search volume rate for the last month, search volume trend for the previous 12 months, as well as current cost-per-click and competition values. Moreover, this endpoint supplies minimum, maximum and average values of daily impressions, clicks and CPC for each result.

Datasource: DataForSEO SERPs Database
Search algorithm: depth-first search for queries appearing in the "search related to" element of SERP for the specified seed keyword.
`;
  }

  getParams(): z.ZodRawShape {
    return {
      keyword: z.string().describe(`target keyword`),
      depth: z.number().min(0).max(4).default(1).describe(`keyword search depth`),
      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_data.keyword_info.search_volume",">",0]
[["keyword_info.search_volume","in",[0,1000]],
"and",
["keyword_data.keyword_info.competition_level","=","LOW"]]

[["keyword_data.keyword_info.search_volume",">",100],
"and",
[["keyword_data.keyword_info.cpc","<",0.5],
"or",
["keyword_data.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 type
example:
["keyword_data.keyword_info.competition,desc"]
default rule:
["keyword_data.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_data.keyword_info.search_volume,desc","keyword_data.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/related_keywords/live', 'POST', [{
        keyword: params.keyword,
        location_name: params.location_name,
        language_code: params.language_code,
        depth: params.depth,  
        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/dataforseo-labs-api.module.ts:
--------------------------------------------------------------------------------

```typescript
import { DataForSEOClient } from '../../client/dataforseo.client.js';
import { BaseModule, ToolDefinition } from '../base.module.js';
import { PromptDefinition } from '../prompt-definition.js';
import { GoogleDomainCompetitorsTool } from './tools/google/competitor-research/google-domain-competitors.tool.js';
import { GoogleDomainRankOverviewTool } from './tools/google/competitor-research/google-domain-rank-overview.tool.js';
import { GoogleKeywordsIdeasTool } from './tools/google/keyword-research/google-keywords-ideas.tool.js';
import { GoogleKeywordsSuggestionsTool } from './tools/google/keyword-research/google-keywords-suggestions.tool.js';
import { GoogleRankedKeywordsTool } from './tools/google/competitor-research/google-ranked-keywords.tool.js';
import { GoogleRelatedKeywordsTool } from './tools/google/keyword-research/google-related-keywords.tool.js';
import { GoogleBulkKeywordDifficultyTool } from './tools/google/keyword-research/google-bulk-keyword-difficulty.tool.js';
import { GoogleTopSearchesTool } from './tools/google/market-analysis/google-top-searches.tool.js';
import { GoogleKeywordOverviewTool } from './tools/google/keyword-research/google-keyword-overview.tool.js';
import { GoogleKeywordsForSiteTool } from './tools/google/keyword-research/google-keywords-for-site.tool.js';
import { GoogleSubdomainsTool } from './tools/google/competitor-research/google-subdomains.js';
import { GoogleSERPCompetitorsTool } from './tools/google/competitor-research/google-serp-competitors.tool.js';
import { GoogleHistoricalSERP } from './tools/google/competitor-research/google-historical-serp.js';
import { GoogleSearchIntentTool } from './tools/google/keyword-research/google-search-intent.tool.js';
import { GoogleDomainIntersectionsTool } from './tools/google/competitor-research/google-domain-intersection.tool.js';
import { GoogleHistoricalDomainRankOverviewTool } from './tools/google/competitor-research/google-historical-domain-rank-overview.tool.js';
import { GooglePageIntersectionsTool } from './tools/google/competitor-research/google-page-intersection.tool.js';
import { DataForSeoLabsFilterTool } from './tools/labs-filters.tool.js';
import { GoogleBulkTrafficEstimationTool } from './tools/google/competitor-research/google-bulk-traffic-estimation.tool.js';
import { GoogleHistoricalKeywordDataTool } from './tools/google/keyword-research/google-historical-keyword-data.tool.js';
import { GoogleRelevantPagesTool } from './tools/google/competitor-research/google-relevant-pages.js';
import { datalabsPrompts } from './dataforseo-labs.prompts.js';

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

  getTools(): Record<string, ToolDefinition> {
    const tools = [
      new GoogleRankedKeywordsTool(this.dataForSEOClient),
      new GoogleDomainCompetitorsTool(this.dataForSEOClient),
      new GoogleDomainRankOverviewTool(this.dataForSEOClient),
      new GoogleKeywordsIdeasTool(this.dataForSEOClient),
      new GoogleRelatedKeywordsTool(this.dataForSEOClient),
      new GoogleKeywordsSuggestionsTool(this.dataForSEOClient),
      new GoogleHistoricalSERP(this.dataForSEOClient),
      new GoogleSERPCompetitorsTool(this.dataForSEOClient),
      new GoogleBulkKeywordDifficultyTool(this.dataForSEOClient),
      new GoogleSubdomainsTool(this.dataForSEOClient),
      new GoogleKeywordOverviewTool(this.dataForSEOClient),
      new GoogleTopSearchesTool(this.dataForSEOClient),
      new GoogleSearchIntentTool(this.dataForSEOClient),
      new GoogleKeywordsForSiteTool(this.dataForSEOClient),
      new GoogleDomainIntersectionsTool(this.dataForSEOClient),
      new GoogleHistoricalDomainRankOverviewTool(this.dataForSEOClient),
      new GooglePageIntersectionsTool(this.dataForSEOClient),
      new GoogleBulkTrafficEstimationTool(this.dataForSEOClient),
      new DataForSeoLabsFilterTool(this.dataForSEOClient),
      new GoogleHistoricalKeywordDataTool(this.dataForSEOClient),
      new GoogleRelevantPagesTool(this.dataForSEOClient),
      // Add more tools here
    ];

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

          return tool.handle(params);
        },
      },
    }), {});
  }

    getPrompts(): Record<string, PromptDefinition> {
      return datalabsPrompts.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-keywords-ideas.tool.ts:
--------------------------------------------------------------------------------

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

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

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

  getDescription(): string {
    return `The Keyword Ideas provides search terms that are relevant to the product or service categories of the specified keywords. The algorithm selects the keywords which fall into the same categories as the seed keywords specified in a POST array.
As a result, you will get a list of relevant keyword ideas for up to 200 seed keywords.
Along with each keyword idea, you will get its search volume rate for the last month, search volume trend for the previous 12 months, as well as current cost-per-click and competition values. Moreover, this endpoint supplies minimum, maximum and average values of daily impressions, clicks and CPC for each result.
`;
  }

  getParams(): z.ZodRawShape {

    return {
      keywords: z.array(z.string()).describe(`target keywords`),
      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"]
relevance is used as the default sorting rule to provide you with the closest keyword ideas. We recommend using this sorting rule to get highly-relevant search terms. Note that relevance is only our internal system identifier, so it can not be used as a filter, and you will not find this field in the result array. The relevance score is based on a similar principle as used in the Keywords For Keywords endpoint.
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:
["relevance,desc","keyword_info.search_volume,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/keyword_ideas/live', 'POST', [{
        keywords: params.keywords,
        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/competitor-research/google-relevant-pages.ts:
--------------------------------------------------------------------------------

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

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

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

  getDescription(): string {
    return `This endpoint will provide you with rankings and traffic data for the web pages of the specified domain. You will be able to review each page’s ranking distribution and estimated monthly traffic volume from both organic and paid searches.`;
  }

  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`),
          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:
["metrics.organic.count",">",50]
[["metrics.organic.pos_1","<>",0],"and",["metrics.organic.impressions_etv",">=","10"]]

[[["metrics.organic.count",">=",50],"and",["metrics.organic.pos_1","in",[1,5]]],
"or",
["metrics.organic.etv",">=","100"]]`
          ),
          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 specify a sorting type
example:
["metrics.paid.etv,asc"]
Note: you can set no more than three sorting rules in a single request
you should use a comma to separate several sorting rules
example:
["metrics.organic.etv,desc","metrics.paid.count,asc"]
default rule:
["metrics.organic.count,desc"]`
          ),
          exclude_top_domains: z.boolean().default(true).describe(`indicates whether to exclude world’s largest websites
optional field
default value: false
set to true if you want to get highly-relevant competitors excluding the top websites`) ,
          item_types: z.array(z.string()).optional().describe(
            `item types to return
            optional field
            default: ['organic']
            possible values:
            organic
            paid`
        ),
          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/relevant_pages/live', 'POST', [{
        target: params.target,
        location_name: params.location_name,
        language_code: params.language_code,
        ignore_synonyms: params.ignore_synonyms,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        exclude_top_domains: params.exclude_top_domains,
        item_types: params.item_types,
        include_clickstream_data: params.include_clickstream_data,
        limit: params.limit,
        offset: params.offset
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import { z } from 'zod';
import { DataForSEOClient } from '../client/dataforseo.client.js';
import { defaultGlobalToolConfig } from '../config/global.tool.js';
import { filterFields, parseFieldPaths } from '../utils/field-filter.js';
import { FieldConfigurationManager } from '../config/field-configuration.js';

export interface DataForSEOFullResponse {
  version: string;
  status_code: number;
  status_message: string;
  time: string;
  cost: number;
  tasks_count: number;
  tasks_error: number;
  tasks: Array<{
    id: string;
    status_code: number;
    status_message: string;
    time: string;
    cost: number;
    result_count: number;
    path: string[];
    data: Record<string, any>;
    result: any[];
  }>;
}

export interface DataForSEOResponse {
  id: string;
  status_code: number;
  status_message: string;
  items: any[];
}

export abstract class BaseTool {
  protected dataForSEOClient: DataForSEOClient;

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

  protected supportOnlyFullResponse(): boolean {
    return false;
  }

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

  protected getFilterExpression(): z.ZodType<any> {
    if( defaultGlobalToolConfig.simpleFilter ) {
      return z.array(z.any());
    }
    const filterExpression = 
    z.array(
        z.union([
          z.array(z.union([z.string(), z.number(), z.boolean()])).length(3),
          z.enum(["and", "or"]),
          z.array(z.unknown()).length(3),
          z.union([z.string(), z.number(),z.unknown()]),
          z.any()  
        ])
      ).max(3);
    return filterExpression;
  }

  protected validateAndFormatResponse(response: any): { content: Array<{ type: string; text: string }> } {
    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);
    }
    this.validateResponse(response);
    return this.formatResponse(response);
  }

  protected formatResponse(data: any): { content: Array<{ type: string; text: string }> } {
    const fieldConfig = FieldConfigurationManager.getInstance();
    if (fieldConfig.hasConfiguration()) {
      const toolName = this.getName();
      if (fieldConfig.isToolConfigured(toolName)) {
        const fields = fieldConfig.getFieldsForTool(toolName);
        if (fields && fields.length > 0) {
          data = filterFields(data, parseFieldPaths(fields));
        }
      }
    }
    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)}`,
        },
      ],
    };
  }

  protected validateResponse(response: DataForSEOResponse): void {
    if (response.status_code / 100 !== 200) {
      throw new Error(`API Error: ${response.status_message} (Code: ${response.status_code})`);
    }
  }

  protected validateResponseFull(response: DataForSEOFullResponse): void {
    if (response.status_code / 100 !== 200) {
      throw new Error(`API Error: ${response.status_message} (Code: ${response.status_code})`);
    }

    if (response.tasks.length === 0) {
      throw new Error('No tasks in response');
    }

    const task = response.tasks[0];
    if (task.status_code / 100 !== 200) {
      throw new Error(`Task Error: ${task.status_message} (Code: ${task.status_code})`);
    }

    if (response.tasks_error > 0) {
      throw new Error(`Tasks Error: ${response.tasks_error} tasks failed`);
    }
  }

  abstract getName(): string;
  abstract getDescription(): string;
  abstract getParams(): z.ZodRawShape;
  abstract handle(params: any): Promise<any>;

  protected filterResponseFields(response: any, fields: string[]): any {
    if (!fields || fields.length === 0) {
      return response;
    }

    const fieldPaths = parseFieldPaths(fields);
    return filterFields(response, fieldPaths);
  }

  protected formatFilters(filters: any[]): any {
    if (!filters)
      return null;
    if (filters.length === 0) {
      return null;
    }
    return this.removeNested(filters);
  }

  private removeNested(filters: any[]): any[] {
    for (var i = 0; i < filters.length; i++) {
      if (Array.isArray(filters[i]) && filters[i].length == 1 && Array.isArray(filters[i][0])) {
        filters[i] = this.removeNested(filters[i][0]);
      }
    }
    return filters;
  }

  protected formatOrderBy(orderBy: any[]): any {
    if (!orderBy)
      return null;
    if (orderBy.length === 0) {
      return null;
    }
    return orderBy;
  }
} 

```

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

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

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

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

  getDescription(): string {
    return `This endpoint will provide you with the keywords for which both specified domains rank within the same SERP. You will get search volume, competition, cost-per-click and impressions data on each intersecting keyword. Along with that, you will get data on the first and second domain's SERP element discovered for this keyword, as well as the estimated traffic volume and cost of ad traffic.`;
  }

  getParams(): z.ZodRawShape {
    return {
      target1: z.string().describe(`target domain 1`),
      target2: z.string().describe(`target domain 2 `),
      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`),
      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_data.keyword_info.search_volume","in",[100,1000]]
        [["first_domain_serp_element.etv",">",0],"and",["first_domain_serp_element.description","like","%goat%"]]
        [["keyword_data.keyword_info.search_volume",">",100],"and",[["first_domain_serp_element.description","like","%goat%"],"or",["second_domain_serp_element.type","=","organic"]]]`
      ),
      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:
["keyword_data.keyword_info.competition,desc"]
default rule:
["keyword_data.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_data.keyword_info.search_volume,desc","keyword_data.keyword_info.cpc,desc"]`
      ),
      intersections: z.boolean().optional().describe(`domain intersections in SERP
optional field
if you set intersections to true, you will get the keywords for which both target domains specified as target1 and target2 have results within the same SERP; the corresponding SERP elements for both domains will be provided in the results array
Note: this endpoint will not provide results if the number of intersecting keywords exceeds 10 million
if you specify intersections: false, you will get the keywords for which the domain specified as target1 has results in SERP, and the domain specified as target2 doesn’t;
thus, the corresponding SERP elements and other data will be provided for the domain specified as target1only
default value: true`).default(true),
      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/domain_intersection/live', 'POST', [{
        target1: params.target1,
        target2: params.target2,
        location_name: params.location_name,
        language_code: params.language_code,
        ignore_synonyms: params.ignore_synonyms,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        exclude_top_domains: params.exclude_top_domains,
        item_types: ['organic'],
        intersections: params.intersections,
        include_clickstream_data: params.include_clickstream_data,
        limit: params.limit,
        offset: params.offset
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/labs-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]: {
    [engine: string]: {
      [field: string]: string;
    };
  };
}

export class DataForSeoLabsFilterTool 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 } = {
    'dataforseo_labs_google_ranked_keywords': 'ranked_keywords.google',
    'dataforseo_labs_google_keyword_ideas': 'keyword_ideas.google',
    'dataforseo_labs_google_keywords_for_site': 'keywords_for_site.google',
    'dataforseo_labs_google_competitors_domain': 'competitors_domain.google',
    'dataforseo_labs_google_serp_competitors': 'serp_competitors.google',
    'dataforseo_labs_google_subdomains': 'subdomains.google',
    'dataforseo_labs_google_domain_intersection': 'domain_intersection.google',
    'dataforseo_labs_google_page_intersection': 'page_intersection.google',
    'dataforseo_labs_google_historical_serp': 'historical_serp.google',
    'dataforseo_labs_google_historical_rank_overview': 'domain_rank_overview.google',
    'dataforseo_labs_google_relevant_pages': 'relevant_pages.google',
    'dataforseo_labs_google_top_searches': 'top_searches.google',
    'dataforseo_labs_google_keyword_overview': 'keyword_overview.google',
    'dataforseo_labs_google_search_intent': 'search_intent.google',
    'dataforseo_labs_google_bulk_keyword_difficulty': 'bulk_keyword_difficulty.google',
    'dataforseo_labs_google_related_keywords': 'related_keywords.google',
    'dataforseo_labs_google_keyword_suggestions': 'keyword_suggestions.google',
    'dataforseo_labs_google_domain_rank_overview': 'domain_rank_overview.google',
    'dataforseo_labs_google_domain_metrics_by_categories': 'domain_metrics_by_categories.google',
    'dataforseo_labs_google_domain_whois_overview': 'domain_whois_overview.google',
    'dataforseo_labs_google_categories_for_domain': 'categories_for_domain.google',
    'dataforseo_labs_google_keywords_for_categories': 'keywords_for_categories.google',
    'dataforseo_labs_amazon_product_competitors': 'product_competitors.amazon',
    'dataforseo_labs_amazon_product_keyword_intersections': 'product_keyword_intersections.amazon',
    'dataforseo_labs_google_app_competitors': 'app_competitors.google',
    'dataforseo_labs_apple_app_competitors': 'app_competitors.apple',
    'dataforseo_labs_google_app_intersection': 'app_intersection.google',
    'dataforseo_labs_apple_app_intersection': 'app_intersection.apple',
    'dataforseo_labs_google_keywords_for_app': 'keywords_for_app.google',
    'dataforseo_labs_apple_keywords_for_app': 'keywords_for_app.apple',
    'dataforseo_labs_database_rows_count': 'database_rows_count'
  };

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

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

  getDescription(): string {
    return `Here you will find all the necessary information about filters that can be used with DataForSEO Labs 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 (DataForSeoLabsFilterTool.cache && 
        (now - DataForSeoLabsFilterTool.lastFetchTime) < DataForSeoLabsFilterTool.CACHE_TTL) {
      return DataForSeoLabsFilterTool.cache;
    }

    // Fetch fresh data
    const response = await this.client.makeRequest('/v3/dataforseo_labs/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(DataForSeoLabsFilterTool.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
    DataForSeoLabsFilterTool.cache = filters;
    DataForSeoLabsFilterTool.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/dataforseo-labs/dataforseo-labs.prompts.ts:
--------------------------------------------------------------------------------

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


export const datalabsPrompts: PromptDefinition[] = [
  {
    name: 'create_content_targeting_decision_stage_users',
    title: 'Create content targeting decision-stage users',
    params: {
      product: z.string().describe('The product to search related keywords for')
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `What are alternative or comparison ("vs", "alternative", "best", "compare") search queries people use for ${params.product}? Return 20 ideas with high search volume.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'generate_seo_friendly_article_ideas',
    title: 'Generate SEO-friendly article ideas that directly answer user questions.',
    params: {
      topic: z.string().describe('topic'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Show me 20 question-based keywords (what, why, how) for '${params.topic}' with search volume ≥ 300. Include search intent and suggest article headlines that match the query tone.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'focus_on_high_converting_terms_for_paid_campaigns_based_on_buyer_readiness',
    title: 'Focus on high-converting terms for paid campaigns based on buyer readiness.',
    params: {
      product: z.string().describe('The product/service to compare'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Find 20 commercial and transactional keywords related to '${params.product}'. Filter by CPC ≥ $2 and search volume ≥ 1,000. Suggest landing page angles based on intent.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'structure_site_content_and_internal_linking_based_on_keyword_clusters',
    title: 'Structure site content and internal linking based on keyword clusters.',
    params: {
      keyword: z.string().describe('The keyword to cluster related keywords for'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Provide a 20-term keyword cluster around ${params.keyword}. Group them by intent (informational, commercial, transactional) and list keyword difficulty and SERP features.`
            }
          }
        ]
      };
    }
  },
    {
    name: 'compare_sites_by_keywords',
    title: 'Competitor Comparison',
    params: {
      site_1: z.string().describe('The first site to compare'),
      site_2: z.string().describe('The second site to compare'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Create a competitor comparison matrix between ${params.site_1} and ${params.site_2} based on keyword overlap and backlink profile.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'build_content_that_aligns_with_user_research_behavior_and_ranks_easier',
    title: 'Build content that aligns with user research behavior and ranks easier.',
    params: {
      topic: z.string().describe('keyword/topic'),
    },
    handler: async (params) => {
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Give me 20 informational keywords around '${params.topic}' with low competition and moderate search volume (≥500). Group them by intent and suggest blog topics for each..`
            }
          }
        ]
      };
    }
  },
  {
    name: 'track_long_term_seo_performance',
    title: 'Track long-term SEO performance, visibility shifts, and seasonal trends.',
    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: `Using google_historical_rank_overview in DataForSEO Labs API, show how the visibility and SERP position distribution of '${params.domain}' changed in ${params.location} in ${params.language} over the past 12 months. Focus on the top 3, top 10, and top 100 rankings, and highlight any traffic peaks.`
            }
          }
        ]
      };
    }
  },
  {
    name: 'compare_monthly_organic_traffic_trends_and_ranking_distribution_against_a_competitor',
    title: 'Compare monthly organic traffic trends and ranking distribution against a competitor.',
    params: {
      domain: z.string().describe('Your domain to analyze'),
      competitor_domain: z.string().describe('Competitor domain to compare against'),
      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: `Compare the monthly organic traffic trends and ranking distribution of ${params.domain} vs ${params.competitor_domain} in ${params.location} in ${params.language} using google_domain_rank_overview. Highlight who has better top 10 visibility and estimated traffic this month.`
            }
          }
        ]
      };
    }
  },
];

```

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

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { DataForSEOClient, DataForSEOConfig } from '../core/client/dataforseo.client.js';
import { SerpApiModule } from '../core/modules/serp/serp-api.module.js';
import { KeywordsDataApiModule } from '../core/modules/keywords-data/keywords-data-api.module.js';
import { OnPageApiModule } from '../core/modules/onpage/onpage-api.module.js';
import { DataForSEOLabsApi } from '../core/modules/dataforseo-labs/dataforseo-labs-api.module.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 { BacklinksApiModule } from "../core/modules/backlinks/backlinks-api.module.js";
import { BusinessDataApiModule } from "../core/modules/business-data-api/business-data-api.module.js";
import { DomainAnalyticsApiModule } from "../core/modules/domain-analytics/domain-analytics-api.module.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express, { Request as ExpressRequest, Response, NextFunction } from "express";
import { randomUUID } from "node:crypto";
import { GetPromptResult, isInitializeRequest, ReadResourceResult, ServerNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
import { name, version } from '../core/utils/version.js';
import { ModuleLoaderService } from "../core/utils/module-loader.js";
import { initializeFieldConfiguration } from '../core/config/field-configuration.js';
import { initMcpServer } from "./init-mcp-server.js";

// Initialize field configuration if provided
initializeFieldConfiguration();

// Extended request interface to include auth properties
interface Request extends ExpressRequest {
  username?: string;
  password?: string;
}

console.error('Starting DataForSEO MCP Server...');
console.error(`Server name: ${name}, version: ${version}`);

function getSessionId() {
  return randomUUID().toString();
}

async function main() {
  const app = express();
  app.use(express.json());

  // Basic Auth Middleware
  const basicAuth = (req: Request, res: Response, next: NextFunction) => {
    // Check for Authorization header
    const authHeader = req.headers.authorization;
    console.error(authHeader)
    if (!authHeader || !authHeader.startsWith('Basic ')) {
      next();
      return;
    }

    // Extract credentials
    const base64Credentials = authHeader.split(' ')[1];
    const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8');
    const [username, password] = credentials.split(':');

    if (!username || !password) {
      console.error('Invalid credentials');
      res.status(401).json({
        jsonrpc: "2.0",
        error: {
          code: -32001, 
          message: "Invalid credentials"
        },
        id: null
      });
      return;
    }

    // Add credentials to request
    req.username = username;
    req.password = password;
    next();
  };

  const handleMcpRequest = async (req: Request, res: Response) => {
    // In stateless mode, create a new instance of transport and server for each request
    // to ensure complete isolation. A single instance would cause request ID collisions
    // when multiple clients connect concurrently.
    
    try {
      
      // Check if we have valid credentials
      if (!req.username && !req.password) {
        // If no request auth, check environment variables
        const envUsername = process.env.DATAFORSEO_USERNAME;
        const envPassword = process.env.DATAFORSEO_PASSWORD;
        if (!envUsername || !envPassword) {
          console.error('No DataForSEO credentials provided');
          res.status(401).json({
            jsonrpc: "2.0",
            error: {
              code: -32001,
              message: "Authentication required. Provide DataForSEO credentials."
            },
            id: null
          });
          return;
        }
        // Use environment variables
        req.username = envUsername;
        req.password = envPassword;
      }
      
      const server = initMcpServer(req.username, req.password); 
      console.error(Date.now().toLocaleString())

      const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined
      });

      await server.connect(transport);
      console.error('handle request');
      await transport.handleRequest(req , res, req.body);
      console.error('end handle request');
      req.on('close', () => {
        console.error('Request closed');
        transport.close();
        server.close();
      });

    } catch (error) {
      console.error('Error handling MCP request:', error);
      if (!res.headersSent) {
        res.status(500).json({
          jsonrpc: '2.0',
          error: {
            code: -32603,
            message: 'Internal server error',
          },
          id: null,
        });
      }
    }
  };

  const handleNotAllowed = (method: string) => async (req: Request, res: Response) => {
    console.error(`Received ${method} request`);
    res.status(405).json({
      jsonrpc: "2.0",
      error: {
        code: -32000,
        message: "Method not allowed."
      },
      id: null
    });
  };

  // Apply basic auth and shared handler to both endpoints
  app.post('/http', basicAuth, handleMcpRequest);
  app.post('/mcp', basicAuth, handleMcpRequest);

  app.get('/http', handleNotAllowed('GET HTTP'));
  app.get('/mcp', handleNotAllowed('GET MCP'));

  app.delete('/http', handleNotAllowed('DELETE HTTP'));
  app.delete('/mcp', handleNotAllowed('DELETE MCP'));

  // Start the server
  const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
  app.listen(PORT, () => {
    console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
  });
}

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

```

--------------------------------------------------------------------------------
/src/core/modules/dataforseo-labs/tools/google/competitor-research/google-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 GooglePageIntersectionsTool extends BaseTool {
  constructor(private client: DataForSEOClient) {
    super(client);
  }

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

  getDescription(): string {
    return `This endpoint will provide you with the keywords for which specified pages rank within the same SERP. You will get search volume, competition, cost-per-click and impressions data on each intersecting keyword. Along with that, you will get data on SERP elements that specified pages rank for in search results, as well as the estimated traffic volume and cost of ad traffic. Page Intersection endpoint supports organic, paid, local pack and featured snippet results.

Find keywords several webpages rank for:
If you would like to get the keywords several pages rank for, you need to specify webpages only in the pages object. This way, you will receive intersected ranked keywords for the specified URLs.

Find keywords your competitors rank for but you do not:
If you would like to receive all keywords several pages rank for, but particular pages do not, you need to use the exclude_pages array as well. This way you will receive the keywords for which the URLs from the pages object rank for, but the URLs from the exclude_pages array do not`;
  }

  getParams(): z.ZodRawShape {
    return {
      pages: z.array(z.string()).describe(`pages array
required field
you can set up to 20 pages in this object
the pages should be specified with absolute URLs (including http:// or https://)
if you specify a single page here, we will return results only for this page;
you can also use a wildcard ('*') character to specify the search pattern
example:
"example.com"
search for the exact URL
"example.com/eng/*"
search for the example.com page and all its related URLs which start with '/eng/', such as "example.com/eng/index.html" and "example.com/eng/help/", etc.
note: a wilcard should be placed after the slash ('/') character in the end of the URL, it is not possible to place it after the domain in the following way:
https://dataforseo.com*
use https://dataforseo.com/* instead`),
      exclude_pages: z.array(z.string()).optional().describe(`URLs of pages you want to exclude
optional field
you can set up to 10 pages in this array
if you use this array, results will contain the keywords for which URLs from the pages object rank, but URLs from exclude_pages array do not;
note that if you specify this field, the results will be based on the keywords any URL from pages ranks for regardless of intersections between them. However, you can set intersection_mode to intersect and results will contain the keywords all URLs from pages rank for in the same SERP and URLs from exclude_pages do not.
use a wildcard (‘*’) character to specify the search pattern
example:
"exclude_pages": [
"https://www.apple.com/iphone/*",
"https://dataforseo.com/apis/*",
"https://www.microsoft.com/en-us/industry/services/"
]`),
       intersection_mode: z.enum(['union', 'intersect']).optional().describe(`indicates whether to intersect keywords
optional field
use this field to intersect or merge results for the specified URLs
possible values: union, intersect

union – results are based on all keywords any URL from pages rank for;

intersect – results are based on the keywords all URLs from pages rank for in the same SERP:

by default, results are based on the intersect mode if you specify only pages array. If you specify exclude_pages as well, results are based on the union mode`),
      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`),
      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_data.keyword_info.search_volume","in",[100,1000]]
        [["intersection_result.1.etv",">",0],"and",["intersection_result.2.description","like","%goat%"]]
        [["keyword_data.keyword_info.search_volume",">",100],"and",[["intersection_result.1.description","like","%goat%"],"or",["intersection_result.2.type","=","organic"]]]`
      ),
      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:
["keyword_data.keyword_info.competition,desc"]
default rule:
["keyword_data.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:
["intersection_result.1.rank_group,asc","intersection_result.2.rank_absolute,asc"]`
      ),      
      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/page_intersection/live', 'POST', [{
        pages: mapArrayToNumberedKeys(params.pages),
        location_name: params.location_name,
        language_code: params.language_code,
        ignore_synonyms: params.ignore_synonyms,
        filters: this.formatFilters(params.filters),
        order_by: this.formatOrderBy(params.order_by),
        exclude_top_domains: params.exclude_top_domains,
        item_types: ['organic'],
        exclude_pages: params.exclude_pages,
        intersection_mode: params.intersection_mode,
        limit: params.limit,
        offset: params.offset,
        include_clickstream_data: params.include_clickstream_data
      }]);
      return this.validateAndFormatResponse(response);
    } catch (error) {
      return this.formatErrorResponse(error);
    }
  }
} 
```

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

```typescript
import express, { Request as ExpressRequest, Response, NextFunction } from 'express';
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { z } from 'zod';
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { DataForSEOClient, DataForSEOConfig } from '../core/client/dataforseo.client.js';
import { EnabledModulesSchema, isModuleEnabled } from '../core/config/modules.config.js';
import { BaseModule, ToolDefinition } from '../core/modules/base.module.js';
import { name, version } from '../core/utils/version.js';
import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js';
import { ModuleLoaderService } from '../core/utils/module-loader.js';
import { initializeFieldConfiguration } from '../core/config/field-configuration.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}`);

/**
 * This example server demonstrates backwards compatibility with both:
 * 1. The deprecated HTTP+SSE transport (protocol version 2024-11-05)
 * 2. The Streamable HTTP transport (protocol version 2025-03-26)
 * 
 * It maintains a single MCP server instance but exposes two transport options:
 * - /mcp: The new Streamable HTTP endpoint (supports GET/POST/DELETE)
 * - /sse: The deprecated SSE endpoint for older clients (GET to establish stream)
 * - /messages: The deprecated POST endpoint for older clients (POST to send messages)
 */

// Configuration constants
const CONNECTION_TIMEOUT = 30000; // 30 seconds
const CLEANUP_INTERVAL = 60000; // 1 minute

// Extended request interface to include auth properties
interface Request extends ExpressRequest {
  username?: string;
  password?: string;
}

// Transport interface with timestamp
interface TransportWithTimestamp {
  transport: StreamableHTTPServerTransport | SSEServerTransport;
  lastActivity: number;
}

// Store transports by session ID
const transports: Record<string, TransportWithTimestamp> = {};

// Cleanup function for stale connections
function cleanupStaleConnections() {
  const now = Date.now();
  Object.entries(transports).forEach(([sessionId, { transport, lastActivity }]) => {
    if (now - lastActivity > CONNECTION_TIMEOUT) {
      console.log(`Cleaning up stale connection for session ${sessionId}`);
      try {
        transport.close();
      } catch (error) {
        console.error(`Error closing transport for session ${sessionId}:`, error);
      }
      delete transports[sessionId];
    }
  });
}

// Start periodic cleanup
const cleanupInterval = setInterval(cleanupStaleConnections, CLEANUP_INTERVAL);



// Create Express application
const app = express();
app.use(express.json());

// Basic Auth Middleware
const basicAuth = (req: Request, res: Response, next: NextFunction) => {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Basic ')) {
    next();
    return;
  }

  const base64Credentials = authHeader.split(' ')[1];
  const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8');
  const [username, password] = credentials.split(':');

  if (!username || !password) {
    console.error('Invalid credentials');
    res.status(401).json({
      jsonrpc: "2.0",
      error: {
        code: -32001,
        message: "Invalid credentials"
      },
      id: null
    });
    return;
  }

  req.username = username;
  req.password = password;
  next();
};

//=============================================================================
// STREAMABLE HTTP TRANSPORT (PROTOCOL VERSION 2025-03-26)
//=============================================================================

const handleMcpRequest = async (req: Request, res: Response) => {
    // In stateless mode, create a new instance of transport and server for each request
    // to ensure complete isolation. A single instance would cause request ID collisions
    // when multiple clients connect concurrently.
    
    try {
      console.error(Date.now().toLocaleString())
      
    // Handle credentials
      if (!req.username && !req.password) {
        const envUsername = process.env.DATAFORSEO_USERNAME;
        const envPassword = process.env.DATAFORSEO_PASSWORD;
        if (!envUsername || !envPassword) {
          console.error('No DataForSEO credentials provided');
          res.status(401).json({
            jsonrpc: "2.0",
            error: {
              code: -32001,
              message: "Authentication required. Provide DataForSEO credentials."
            },
            id: null
          });
          return;
        }
        req.username = envUsername;
        req.password = envPassword;
      }
      
      const server = initMcpServer(req.username, req.password); 
      console.error(Date.now().toLocaleString())

      const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined
      });

      await server.connect(transport);
      console.error('handle request');
      await transport.handleRequest(req , res, req.body);
      console.error('end handle request');
      req.on('close', () => {
        console.error('Request closed');
        transport.close();
        server.close();
      });

    } catch (error) {
      console.error('Error handling MCP request:', error);
      if (!res.headersSent) {
        res.status(500).json({
          jsonrpc: '2.0',
          error: {
            code: -32603,
            message: 'Internal server error',
          },
          id: null,
        });
      }
    }
  };

const handleNotAllowed = (method: string) => async (req: Request, res: Response) => {
    console.error(`Received ${method} request`);
    res.status(405).json({
      jsonrpc: "2.0",
      error: {
        code: -32000,
        message: "Method not allowed."
      },
      id: null
    });
  };

// Apply basic auth and shared handler to both endpoints
app.post('/http', basicAuth, handleMcpRequest);
app.post('/mcp', basicAuth, handleMcpRequest);

app.get('/http', handleNotAllowed('GET HTTP'));
app.get('/mcp', handleNotAllowed('GET MCP'));

app.delete('/http', handleNotAllowed('DELETE HTTP'));
app.delete('/mcp', handleNotAllowed('DELETE MCP'));

//=============================================================================
// DEPRECATED HTTP+SSE TRANSPORT (PROTOCOL VERSION 2024-11-05)
//=============================================================================

app.get('/sse', basicAuth, async (req: Request, res: Response) => {
  console.log('Received GET request to /sse (deprecated SSE transport)');

  // Handle credentials
  if (!req.username && !req.password) {
    const envUsername = process.env.DATAFORSEO_USERNAME;
    const envPassword = process.env.DATAFORSEO_PASSWORD;
    
    if (!envUsername || !envPassword) {
      console.error('No DataForSEO credentials provided');
      res.status(401).json({
        jsonrpc: "2.0",
        error: {
          code: -32001,
          message: "Authentication required. Provide DataForSEO credentials."
        },
        id: null
      });
      return;
    }
    req.username = envUsername;
    req.password = envPassword;
  }

  const transport = new SSEServerTransport('/messages', res);
  
  // Store transport with timestamp
  transports[transport.sessionId] = {
    transport,
    lastActivity: Date.now()
  };

  // Handle connection cleanup
  const cleanup = () => {
    try {
      transport.close();
    } catch (error) {
      console.error(`Error closing transport for session ${transport.sessionId}:`, error);
    }
    delete transports[transport.sessionId];
  };

  res.on("error", cleanup);
  req.on("error", cleanup);
  req.socket.on("error", cleanup);
  req.socket.on("timeout", cleanup);

  // Set socket timeout
  req.socket.setTimeout(CONNECTION_TIMEOUT);

  const server = initMcpServer(req.username, req.password);
  await server.connect(transport);
});

app.post("/messages", basicAuth, async (req: Request, res: Response) => {
  const sessionId = req.query.sessionId as string;
  
  // Handle credentials
  if (!req.username && !req.password) {
    const envUsername = process.env.DATAFORSEO_USERNAME;
    const envPassword = process.env.DATAFORSEO_PASSWORD;
    
    if (!envUsername || !envPassword) {
      res.status(401).json({
        jsonrpc: "2.0",
        error: {
          code: -32001,
          message: "Authentication required. Provide DataForSEO credentials."
        },
        id: null
      });
      return;
    }
    req.username = envUsername;
    req.password = envPassword;
  }

  const transportData = transports[sessionId];
  if (!transportData) {
    res.status(400).send('No transport found for sessionId');
    return;
  }

  if (!(transportData.transport instanceof SSEServerTransport)) {
    res.status(400).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: 'Bad Request: Session exists but uses a different transport protocol',
      },
      id: null,
    });
    return;
  }

  // Update last activity timestamp
  transportData.lastActivity = Date.now();
  
  await transportData.transport.handlePostMessage(req, res, req.body);
});

// Start the server
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
const server = app.listen(PORT, () => {
  console.log(`DataForSEO MCP Server with SSE compatibility listening on port ${PORT}`);
  console.log(`
==============================================
SUPPORTED TRANSPORT OPTIONS:

1. Streamable Http (Protocol version: 2025-03-26)
   Endpoint: /http (POST)
   Endpoint: /mcp (POST)


2. Http + SSE (Protocol version: 2024-11-05)
   Endpoints: /sse (GET) and /messages (POST)
   Usage:
     - Establish SSE stream with GET to /sse
     - Send requests with POST to /messages?sessionId=<id>
==============================================
`);
});

// Handle server shutdown
process.on('SIGINT', async () => {
  console.log('Shutting down server...');
  
  // Clear cleanup interval
  clearInterval(cleanupInterval);

  // Close HTTP server
  server.close();

  // Close all active transports
  for (const sessionId in transports) {
    try {
      console.log(`Closing transport for session ${sessionId}`);
      await transports[sessionId].transport.close();
      delete transports[sessionId];
    } catch (error) {
      console.error(`Error closing transport for session ${sessionId}:`, error);
    }
  }
  console.log('Server shutdown complete');
  process.exit(0);
});
```

--------------------------------------------------------------------------------
/field-config.example.json:
--------------------------------------------------------------------------------

```json
{
    "supported_fields": {
        "serp_organic_live_advanced": [
            "id",
            "items.asynchronous_ai_overview",
            "items.breadcrumb",
            "items.description",
            "items.domain",
            "items.highlighted",
            "items.is_video",
            "items.items.consideration_category",
            "items.items.data_attrid",
            "items.items.date",
            "items.items.description",
            "items.items.domain",
            "items.items.expanded_element.breadcrumb",
            "items.items.expanded_element.description",
            "items.items.expanded_element.domain",
            "items.items.expanded_element.featured_title",
            "items.items.expanded_element.items.references.domain",
            "items.items.expanded_element.items.references.source",
            "items.items.expanded_element.items.references.text",
            "items.items.expanded_element.items.references.title",
            "items.items.expanded_element.items.references.type",
            "items.items.expanded_element.items.references.url",
            "items.items.expanded_element.items.text",
            "items.items.expanded_element.items.title",
            "items.items.expanded_element.items.type",
            "items.items.expanded_element.references.domain",
            "items.items.expanded_element.references.source",
            "items.items.expanded_element.references.text",
            "items.items.expanded_element.references.title",
            "items.items.expanded_element.references.type",
            "items.items.expanded_element.references.url",
            "items.items.expanded_element.related_searches",
            "items.items.expanded_element.snippet",
            "items.items.expanded_element.timestamp",
            "items.items.expanded_element.title",
            "items.items.expanded_element.type",
            "items.items.expanded_element.url",
            "items.items.items.alt",
            "items.items.items.image_url",
            "items.items.items.type",
            "items.items.links.domain",
            "items.items.links.title",
            "items.items.links.type",
            "items.items.links.url",
            "items.items.source",
            "items.items.text",
            "items.items.timestamp",
            "items.items.title",
            "items.items.type",
            "items.items.url",
            "items.links.domain",
            "items.links.title",
            "items.links.type",
            "items.links.url",
            "items.pre_snippet",
            "items.rank_absolute",
            "items.rank_group",
            "items.subtitle",
            "items.timestamp",
            "items.title",
            "items.type",
            "items.url",
            "items.website_name",
            "status_code",
            "status_message"
        ],
        "keywords_data_google_ads_search_volume": [
            "id",
            "items.competition",
            "items.competition_index",
            "items.cpc",
            "items.high_top_of_page_bid",
            "items.keyword",
            "items.low_top_of_page_bid",
            "items.monthly_searches",
            "items.search_volume",
            "status_code",
            "status_message"
        ],
        "keywords_data_dataforseo_trends_demography": [
            "id",
            "items.demography.age.keyword",
            "items.demography.age.values.type",
            "items.demography.age.values.value",
            "items.demography.gender.keyword",
            "items.demography.gender.values.type",
            "items.demography.gender.values.value",
            "items.keywords",
            "items.type",
            "status_code",
            "status_message"
        ],
        "keywords_data_dataforseo_trends_subregion_interests": [
            "id",
            "items.interests.keyword",
            "items.interests.values.geo_id",
            "items.interests.values.geo_name",
            "items.interests.values.value",
            "items.keywords",
            "items.type",
            "status_code",
            "status_message"
        ],
        "keywords_data_dataforseo_trends_explore": [
            "id",
            "items.averages",
            "items.data.date_from",
            "items.data.date_to",
            "items.data.timestamp",
            "items.data.values",
            "items.keywords",
            "items.type",
            "status_code",
            "status_message"
        ],
        "keywords_data_google_trends_explore": [
            "id",
            "items.data.date_from",
            "items.data.date_to",
            "items.data.missing_data",
            "items.data.timestamp",
            "items.data.values",
            "items.keywords",
            "items.type",
            "status_code",
            "status_message"
        ],
        "on_page_instant_pages": [
            "id",
            "items.cache_control",
            "items.checks",
            "items.content_encoding",
            "items.encoded_size",
            "items.fetch_time",
            "items.last_modified.header",
            "items.media_type",
            "items.meta.charset",
            "items.meta.content.automated_readability_index",
            "items.meta.content.coleman_liau_readability_index",
            "items.meta.content.dale_chall_readability_index",
            "items.meta.content.flesch_kincaid_readability_index",
            "items.meta.content.plain_text_rate",
            "items.meta.content.plain_text_size",
            "items.meta.content.plain_text_word_count",
            "items.meta.content.smog_readability_index",
            "items.meta.content.title_to_content_consistency",
            "items.meta.cumulative_layout_shift",
            "items.meta.external_links_count",
            "items.meta.follow",
            "items.meta.htags.h1",
            "items.meta.title",
            "items.meta.title_length",
            "items.onpage_score",
            "items.page_timing.connection_time",
            "items.page_timing.dom_complete",
            "items.page_timing.duration_time",
            "items.page_timing.fetch_end",
            "items.page_timing.first_input_delay",
            "items.page_timing.largest_contentful_paint",
            "items.page_timing.time_to_interactive",
            "items.page_timing.time_to_secure_connection",
            "items.relative_url_length",
            "items.resource_type",
            "items.size",
            "items.status_code",
            "items.total_dom_size",
            "items.total_transfer_size",
            "items.url",
            "items.url_length",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_ranked_keywords": [
            "id",
            "items.keyword_data.avg_backlinks_info.backlinks",
            "items.keyword_data.avg_backlinks_info.dofollow",
            "items.keyword_data.avg_backlinks_info.last_updated_time",
            "items.keyword_data.avg_backlinks_info.main_domain_rank",
            "items.keyword_data.avg_backlinks_info.rank",
            "items.keyword_data.avg_backlinks_info.referring_domains",
            "items.keyword_data.avg_backlinks_info.referring_main_domains",
            "items.keyword_data.avg_backlinks_info.referring_pages",
            "items.keyword_data.avg_backlinks_info.se_type",
            "items.keyword_data.keyword",
            "items.keyword_data.keyword_info.categories",
            "items.keyword_data.keyword_info.competition",
            "items.keyword_data.keyword_info.competition_level",
            "items.keyword_data.keyword_info.cpc",
            "items.keyword_data.keyword_info.high_top_of_page_bid",
            "items.keyword_data.keyword_info.last_updated_time",
            "items.keyword_data.keyword_info.low_top_of_page_bid",
            "items.keyword_data.keyword_info.monthly_searches",
            "items.keyword_data.keyword_info.se_type",
            "items.keyword_data.keyword_info.search_volume",
            "items.keyword_data.keyword_info.search_volume_trend.monthly",
            "items.keyword_data.keyword_info.search_volume_trend.quarterly",
            "items.keyword_data.keyword_info.search_volume_trend.yearly",
            "items.keyword_data.keyword_properties.core_keyword",
            "items.keyword_data.keyword_properties.detected_language",
            "items.keyword_data.keyword_properties.is_another_language",
            "items.keyword_data.keyword_properties.keyword_difficulty",
            "items.keyword_data.keyword_properties.se_type",
            "items.keyword_data.keyword_properties.synonym_clustering_algorithm",
            "items.keyword_data.language_code",
            "items.keyword_data.location_code",
            "items.keyword_data.se_type",
            "items.keyword_data.search_intent_info.foreign_intent",
            "items.keyword_data.search_intent_info.last_updated_time",
            "items.keyword_data.search_intent_info.main_intent",
            "items.keyword_data.search_intent_info.se_type",
            "items.keyword_data.serp_info.check_url",
            "items.keyword_data.serp_info.last_updated_time",
            "items.keyword_data.serp_info.previous_updated_time",
            "items.keyword_data.serp_info.se_results_count",
            "items.keyword_data.serp_info.se_type",
            "items.keyword_data.serp_info.serp_item_types",
            "items.ranked_serp_element.check_url",
            "items.ranked_serp_element.keyword_difficulty",
            "items.ranked_serp_element.last_updated_time",
            "items.ranked_serp_element.previous_updated_time",
            "items.ranked_serp_element.se_results_count",
            "items.ranked_serp_element.se_type",
            "items.ranked_serp_element.serp_item.backlinks_info.backlinks",
            "items.ranked_serp_element.serp_item.backlinks_info.dofollow",
            "items.ranked_serp_element.serp_item.backlinks_info.referring_domains",
            "items.ranked_serp_element.serp_item.backlinks_info.referring_main_domains",
            "items.ranked_serp_element.serp_item.backlinks_info.referring_pages",
            "items.ranked_serp_element.serp_item.backlinks_info.time_update",
            "items.ranked_serp_element.serp_item.breadcrumb",
            "items.ranked_serp_element.serp_item.description",
            "items.ranked_serp_element.serp_item.domain",
            "items.ranked_serp_element.serp_item.estimated_paid_traffic_cost",
            "items.ranked_serp_element.serp_item.etv",
            "items.ranked_serp_element.serp_item.highlighted",
            "items.ranked_serp_element.serp_item.main_domain",
            "items.ranked_serp_element.serp_item.rank_absolute",
            "items.ranked_serp_element.serp_item.rank_changes.is_down",
            "items.ranked_serp_element.serp_item.rank_changes.is_new",
            "items.ranked_serp_element.serp_item.rank_changes.previous_rank_absolute",
            "items.ranked_serp_element.serp_item.rank_group",
            "items.ranked_serp_element.serp_item.rank_info.main_domain_rank",
            "items.ranked_serp_element.serp_item.rank_info.page_rank",
            "items.ranked_serp_element.serp_item.relative_url",
            "items.ranked_serp_element.serp_item.se_type",
            "items.ranked_serp_element.serp_item.title",
            "items.ranked_serp_element.serp_item.type",
            "items.ranked_serp_element.serp_item.url",
            "items.ranked_serp_element.serp_item.website_name",
            "items.ranked_serp_element.serp_item_types",
            "items.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_competitors_domain": [
            "id",
            "items.avg_position",
            "items.competitor_metrics.organic.count",
            "items.competitor_metrics.organic.estimated_paid_traffic_cost",
            "items.competitor_metrics.organic.etv",
            "items.competitor_metrics.organic.is_down",
            "items.competitor_metrics.organic.is_new",
            "items.competitor_metrics.organic.is_up",
            "items.competitor_metrics.organic.pos_1",
            "items.competitor_metrics.organic.pos_11_20",
            "items.competitor_metrics.organic.pos_21_30",
            "items.competitor_metrics.organic.pos_2_3",
            "items.competitor_metrics.organic.pos_31_40",
            "items.competitor_metrics.organic.pos_41_50",
            "items.competitor_metrics.organic.pos_4_10",
            "items.competitor_metrics.organic.pos_51_60",
            "items.competitor_metrics.organic.pos_61_70",
            "items.competitor_metrics.organic.pos_71_80",
            "items.competitor_metrics.organic.pos_81_90",
            "items.competitor_metrics.organic.pos_91_100",
            "items.domain",
            "items.full_domain_metrics.organic.count",
            "items.full_domain_metrics.organic.estimated_paid_traffic_cost",
            "items.full_domain_metrics.organic.etv",
            "items.full_domain_metrics.organic.is_down",
            "items.full_domain_metrics.organic.is_lost",
            "items.full_domain_metrics.organic.is_new",
            "items.full_domain_metrics.organic.is_up",
            "items.full_domain_metrics.organic.pos_1",
            "items.full_domain_metrics.organic.pos_11_20",
            "items.full_domain_metrics.organic.pos_21_30",
            "items.full_domain_metrics.organic.pos_2_3",
            "items.full_domain_metrics.organic.pos_31_40",
            "items.full_domain_metrics.organic.pos_41_50",
            "items.full_domain_metrics.organic.pos_4_10",
            "items.full_domain_metrics.organic.pos_51_60",
            "items.full_domain_metrics.organic.pos_61_70",
            "items.full_domain_metrics.organic.pos_71_80",
            "items.full_domain_metrics.organic.pos_81_90",
            "items.full_domain_metrics.organic.pos_91_100",
            "items.intersections",
            "items.metrics.*.count",
            "items.metrics.*.estimated_paid_traffic_cost",
            "items.metrics.*.etv",
            "items.metrics.*.is_down",
            "items.metrics.*.is_new",
            "items.metrics.*.is_up",
            "items.metrics.*.pos_1",
            "items.metrics.*.pos_11_20",
            "items.metrics.*.pos_21_30",
            "items.metrics.*.pos_2_3",
            "items.metrics.*.pos_31_40",
            "items.metrics.*.pos_41_50",
            "items.metrics.*.pos_4_10",
            "items.metrics.*.pos_51_60",
            "items.metrics.*.pos_61_70",
            "items.metrics.*.pos_71_80",
            "items.metrics.*.pos_81_90",
            "items.metrics.*.pos_91_100",
            "items.se_type",
            "items.sum_position",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_domain_rank_overview": [
            "id",
            "items.language_code",
            "items.location_code",
            "items.metrics.*.count",
            "items.metrics.*.estimated_paid_traffic_cost",
            "items.metrics.*.etv",
            "items.metrics.*.is_down",
            "items.metrics.*.is_new",
            "items.metrics.*.is_up",
            "items.metrics.*.pos_1",
            "items.metrics.*.pos_11_20",
            "items.metrics.*.pos_21_30",
            "items.metrics.*.pos_2_3",
            "items.metrics.*.pos_31_40",
            "items.metrics.*.pos_41_50",
            "items.metrics.*.pos_4_10",
            "items.metrics.*.pos_51_60",
            "items.metrics.*.pos_61_70",
            "items.metrics.*.pos_71_80",
            "items.metrics.*.pos_81_90",
            "items.metrics.*.pos_91_100",
            "items.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_keyword_ideas": [
            "id",
            "items.avg_backlinks_info.backlinks",
            "items.avg_backlinks_info.dofollow",
            "items.avg_backlinks_info.last_updated_time",
            "items.avg_backlinks_info.main_domain_rank",
            "items.avg_backlinks_info.rank",
            "items.avg_backlinks_info.referring_domains",
            "items.avg_backlinks_info.referring_main_domains",
            "items.avg_backlinks_info.referring_pages",
            "items.avg_backlinks_info.se_type",
            "items.keyword",
            "items.keyword_info.categories",
            "items.keyword_info.competition",
            "items.keyword_info.competition_level",
            "items.keyword_info.cpc",
            "items.keyword_info.high_top_of_page_bid",
            "items.keyword_info.last_updated_time",
            "items.keyword_info.low_top_of_page_bid",
            "items.keyword_info.monthly_searches",
            "items.keyword_info.se_type",
            "items.keyword_info.search_volume",
            "items.keyword_info.search_volume_trend.monthly",
            "items.keyword_info.search_volume_trend.quarterly",
            "items.keyword_info.search_volume_trend.yearly",
            "items.keyword_properties.core_keyword",
            "items.keyword_properties.detected_language",
            "items.keyword_properties.keyword_difficulty",
            "items.keyword_properties.se_type",
            "items.keyword_properties.synonym_clustering_algorithm",
            "items.language_code",
            "items.location_code",
            "items.se_type",
            "items.search_intent_info.foreign_intent",
            "items.search_intent_info.last_updated_time",
            "items.search_intent_info.main_intent",
            "items.search_intent_info.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_related_keywords": [
            "id",
            "items.depth",
            "items.keyword_data.avg_backlinks_info.backlinks",
            "items.keyword_data.avg_backlinks_info.dofollow",
            "items.keyword_data.avg_backlinks_info.last_updated_time",
            "items.keyword_data.avg_backlinks_info.main_domain_rank",
            "items.keyword_data.avg_backlinks_info.rank",
            "items.keyword_data.avg_backlinks_info.referring_domains",
            "items.keyword_data.avg_backlinks_info.referring_main_domains",
            "items.keyword_data.avg_backlinks_info.referring_pages",
            "items.keyword_data.avg_backlinks_info.se_type",
            "items.keyword_data.keyword",
            "items.keyword_data.keyword_info.categories",
            "items.keyword_data.keyword_info.competition",
            "items.keyword_data.keyword_info.competition_level",
            "items.keyword_data.keyword_info.cpc",
            "items.keyword_data.keyword_info.high_top_of_page_bid",
            "items.keyword_data.keyword_info.last_updated_time",
            "items.keyword_data.keyword_info.low_top_of_page_bid",
            "items.keyword_data.keyword_info.monthly_searches",
            "items.keyword_data.keyword_info.se_type",
            "items.keyword_data.keyword_info.search_volume",
            "items.keyword_data.keyword_info.search_volume_trend.monthly",
            "items.keyword_data.keyword_info.search_volume_trend.quarterly",
            "items.keyword_data.keyword_info.search_volume_trend.yearly",
            "items.keyword_data.keyword_properties.core_keyword",
            "items.keyword_data.keyword_properties.detected_language",
            "items.keyword_data.keyword_properties.keyword_difficulty",
            "items.keyword_data.keyword_properties.se_type",
            "items.keyword_data.keyword_properties.synonym_clustering_algorithm",
            "items.keyword_data.language_code",
            "items.keyword_data.location_code",
            "items.keyword_data.se_type",
            "items.keyword_data.search_intent_info.foreign_intent",
            "items.keyword_data.search_intent_info.last_updated_time",
            "items.keyword_data.search_intent_info.main_intent",
            "items.keyword_data.search_intent_info.se_type",
            "items.keyword_data.serp_info.check_url",
            "items.keyword_data.serp_info.last_updated_time",
            "items.keyword_data.serp_info.previous_updated_time",
            "items.keyword_data.serp_info.se_results_count",
            "items.keyword_data.serp_info.se_type",
            "items.keyword_data.serp_info.serp_item_types",
            "items.related_keywords",
            "items.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_keyword_suggestions": [
            "id",
            "items.avg_backlinks_info.backlinks",
            "items.avg_backlinks_info.dofollow",
            "items.avg_backlinks_info.last_updated_time",
            "items.avg_backlinks_info.main_domain_rank",
            "items.avg_backlinks_info.rank",
            "items.avg_backlinks_info.referring_domains",
            "items.avg_backlinks_info.referring_main_domains",
            "items.avg_backlinks_info.referring_pages",
            "items.avg_backlinks_info.se_type",
            "items.keyword",
            "items.keyword_info.categories",
            "items.keyword_info.competition",
            "items.keyword_info.competition_level",
            "items.keyword_info.cpc",
            "items.keyword_info.high_top_of_page_bid",
            "items.keyword_info.last_updated_time",
            "items.keyword_info.low_top_of_page_bid",
            "items.keyword_info.monthly_searches",
            "items.keyword_info.se_type",
            "items.keyword_info.search_volume",
            "items.keyword_info.search_volume_trend.monthly",
            "items.keyword_info.search_volume_trend.yearly",
            "items.keyword_properties.core_keyword",
            "items.keyword_properties.detected_language",
            "items.keyword_properties.keyword_difficulty",
            "items.keyword_properties.se_type",
            "items.keyword_properties.synonym_clustering_algorithm",
            "items.language_code",
            "items.location_code",
            "items.se_type",
            "items.search_intent_info.foreign_intent",
            "items.search_intent_info.last_updated_time",
            "items.search_intent_info.main_intent",
            "items.search_intent_info.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_historical_serp": [
            "datetime",
            "items.domain",
            "items.rank_absolute",
            "items.title",
            "items.type"
        ],
        "dataforseo_labs_google_serp_competitors": [
            "id",
            "items.avg_position",
            "items.domain",
            "items.etv",
            "items.keywords_count",
            "items.keywords_positions.python programming",
            "items.median_position",
            "items.rating",
            "items.relevant_serp_items",
            "items.se_type",
            "items.visibility",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_bulk_keyword_difficulty": [
            "id",
            "items.keyword",
            "items.keyword_difficulty",
            "items.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_subdomains": [
            "id",
            "items.metrics.*.count",
            "items.metrics.*.estimated_paid_traffic_cost",
            "items.metrics.*.etv",
            "items.metrics.*.is_down",
            "items.metrics.*.is_new",
            "items.metrics.*.is_up",
            "items.metrics.*.pos_1",
            "items.metrics.*.pos_11_20",
            "items.metrics.*.pos_21_30",
            "items.metrics.*.pos_2_3",
            "items.metrics.*.pos_31_40",
            "items.metrics.*.pos_41_50",
            "items.metrics.*.pos_4_10",
            "items.metrics.*.pos_51_60",
            "items.metrics.*.pos_61_70",
            "items.metrics.*.pos_71_80",
            "items.metrics.*.pos_81_90",
            "items.metrics.*.pos_91_100",
            "items.se_type",
            "items.subdomain",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_keyword_overview": [
            "id",
            "items.avg_backlinks_info.backlinks",
            "items.avg_backlinks_info.dofollow",
            "items.avg_backlinks_info.last_updated_time",
            "items.avg_backlinks_info.main_domain_rank",
            "items.avg_backlinks_info.rank",
            "items.avg_backlinks_info.referring_domains",
            "items.avg_backlinks_info.referring_main_domains",
            "items.avg_backlinks_info.referring_pages",
            "items.avg_backlinks_info.se_type",
            "items.keyword",
            "items.keyword_info.categories",
            "items.keyword_info.competition",
            "items.keyword_info.competition_level",
            "items.keyword_info.cpc",
            "items.keyword_info.high_top_of_page_bid",
            "items.keyword_info.last_updated_time",
            "items.keyword_info.low_top_of_page_bid",
            "items.keyword_info.monthly_searches",
            "items.keyword_info.se_type",
            "items.keyword_info.search_volume",
            "items.keyword_properties.detected_language",
            "items.keyword_properties.keyword_difficulty",
            "items.keyword_properties.se_type",
            "items.keyword_properties.synonym_clustering_algorithm",
            "items.language_code",
            "items.location_code",
            "items.se_type",
            "items.search_intent_info.foreign_intent",
            "items.search_intent_info.last_updated_time",
            "items.search_intent_info.main_intent",
            "items.search_intent_info.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_top_searches": [
            "id",
            "items.avg_backlinks_info.backlinks",
            "items.avg_backlinks_info.dofollow",
            "items.avg_backlinks_info.last_updated_time",
            "items.avg_backlinks_info.main_domain_rank",
            "items.avg_backlinks_info.rank",
            "items.avg_backlinks_info.referring_domains",
            "items.avg_backlinks_info.referring_main_domains",
            "items.avg_backlinks_info.referring_pages",
            "items.avg_backlinks_info.se_type",
            "items.keyword",
            "items.keyword_info.categories",
            "items.keyword_info.competition",
            "items.keyword_info.competition_level",
            "items.keyword_info.cpc",
            "items.keyword_info.high_top_of_page_bid",
            "items.keyword_info.last_updated_time",
            "items.keyword_info.low_top_of_page_bid",
            "items.keyword_info.monthly_searches",
            "items.keyword_info.se_type",
            "items.keyword_info.search_volume",
            "items.keyword_info.search_volume_trend.quarterly",
            "items.keyword_info.search_volume_trend.yearly",
            "items.keyword_properties.core_keyword",
            "items.keyword_properties.detected_language",
            "items.keyword_properties.is_another_language",
            "items.keyword_properties.keyword_difficulty",
            "items.keyword_properties.se_type",
            "items.keyword_properties.synonym_clustering_algorithm",
            "items.language_code",
            "items.location_code",
            "items.se_type",
            "items.search_intent_info.foreign_intent",
            "items.search_intent_info.last_updated_time",
            "items.search_intent_info.main_intent",
            "items.search_intent_info.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_search_intent": [
            "id",
            "items.keyword",
            "items.keyword_intent.label",
            "items.keyword_intent.probability",
            "items.secondary_keyword_intents.label",
            "items.secondary_keyword_intents.probability",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_keywords_for_site": [
            "id",
            "items.avg_backlinks_info.backlinks",
            "items.avg_backlinks_info.dofollow",
            "items.avg_backlinks_info.last_updated_time",
            "items.avg_backlinks_info.main_domain_rank",
            "items.avg_backlinks_info.rank",
            "items.avg_backlinks_info.referring_domains",
            "items.avg_backlinks_info.referring_main_domains",
            "items.avg_backlinks_info.referring_pages",
            "items.avg_backlinks_info.se_type",
            "items.keyword",
            "items.keyword_info.categories",
            "items.keyword_info.competition",
            "items.keyword_info.competition_level",
            "items.keyword_info.cpc",
            "items.keyword_info.high_top_of_page_bid",
            "items.keyword_info.last_updated_time",
            "items.keyword_info.low_top_of_page_bid",
            "items.keyword_info.monthly_searches",
            "items.keyword_info.se_type",
            "items.keyword_info.search_volume",
            "items.keyword_info.search_volume_trend.monthly",
            "items.keyword_info.search_volume_trend.quarterly",
            "items.keyword_info.search_volume_trend.yearly",
            "items.keyword_properties.detected_language",
            "items.keyword_properties.keyword_difficulty",
            "items.keyword_properties.se_type",
            "items.keyword_properties.synonym_clustering_algorithm",
            "items.language_code",
            "items.location_code",
            "items.se_type",
            "items.search_intent_info.foreign_intent",
            "items.search_intent_info.last_updated_time",
            "items.search_intent_info.main_intent",
            "items.search_intent_info.se_type",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_domain_intersection": [
            "id",
            "items",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_google_historical_rank_overview": [
            "id",
            "items.metrics.*.count",
            "items.metrics.*.estimated_paid_traffic_cost",
            "items.metrics.*.etv",
            "items.metrics.*.is_down",
            "items.metrics.*.is_lost",
            "items.metrics.*.is_new",
            "items.metrics.*.is_up",
            "items.metrics.*.pos_1",
            "items.metrics.*.pos_11_20",
            "items.metrics.*.pos_21_30",
            "items.metrics.*.pos_2_3",
            "items.metrics.*.pos_31_40",
            "items.metrics.*.pos_41_50",
            "items.metrics.*.pos_4_10",
            "items.metrics.*.pos_51_60",
            "items.metrics.*.pos_61_70",
            "items.metrics.*.pos_71_80",
            "items.metrics.*.pos_81_90",
            "items.metrics.*.pos_91_100",
            "items.month",
            "items.se_type",
            "items.year",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_bulk_traffic_estimation": [
            "id",
            "items.se_type",
            "items.target",
            "status_code",
            "status_message"
        ],
        "dataforseo_labs_available_filters": [],
        "dataforseo_labs_google_historical_keyword_data": [
            "id",
            "items.history.keyword_info.categories",
            "items.history.keyword_info.competition",
            "items.history.keyword_info.competition_level",
            "items.history.keyword_info.cpc",
            "items.history.keyword_info.high_top_of_page_bid",
            "items.history.keyword_info.last_updated_time",
            "items.history.keyword_info.low_top_of_page_bid",
            "items.history.keyword_info.monthly_searches",
            "items.history.keyword_info.se_type",
            "items.history.keyword_info.search_volume",
            "items.history.keyword_info.search_volume_trend.monthly",
            "items.history.keyword_info.search_volume_trend.quarterly",
            "items.history.keyword_info.search_volume_trend.yearly",
            "items.history.month",
            "items.history.year",
            "items.keyword",
            "items.language_code",
            "items.location_code",
            "items.se_type",
            "status_code",
            "status_message"
        ],
        "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"
        ],
        "backlinks_anchors": [
            "id",
            "items.anchor",
            "items.backlinks",
            "items.backlinks_spam_score",
            "items.broken_backlinks",
            "items.broken_pages",
            "items.first_seen",
            "items.rank",
            "items.referring_domains",
            "items.referring_domains_nofollow",
            "items.referring_ips",
            "items.referring_links_attributes",
            "items.referring_links_countries",
            "items.referring_links_platform_types",
            "items.referring_links_semantic_locations",
            "items.referring_links_tld",
            "items.referring_links_types",
            "items.referring_main_domains",
            "items.referring_main_domains_nofollow",
            "items.referring_pages",
            "items.referring_pages_nofollow",
            "items.referring_subnets",
            "items.type",
            "status_code",
            "status_message"
        ],
        "backlinks_competitors": [
            "id",
            "items.intersections",
            "items.rank",
            "items.target",
            "items.type",
            "status_code",
            "status_message"
        ],
        "backlinks_domain_intersection": [
            "id",
            "items.domain_intersection.*.backlinks",
            "items.domain_intersection.*.broken_backlinks",
            "items.domain_intersection.*.broken_pages",
            "items.domain_intersection.*.first_seen",
            "items.domain_intersection.*.rank",
            "items.domain_intersection.*.referring_domains",
            "items.domain_intersection.*.referring_ips",
            "items.domain_intersection.*.referring_links_platform_types",
            "items.domain_intersection.*.referring_links_semantic_locations",
            "items.domain_intersection.*.referring_links_tld",
            "items.domain_intersection.*.referring_links_types.anchor",
            "items.domain_intersection.*.referring_links_types.image",
            "items.domain_intersection.*.referring_main_domains",
            "items.domain_intersection.*.referring_pages",
            "items.domain_intersection.*.referring_subnets",
            "items.domain_intersection.*.target",
            "items.domain_intersection.*.type",
            "items.summary.intersections_count",
            "status_code",
            "status_message"
        ],
        "backlinks_domain_pages_summary": [
            "id",
            "items.backlinks",
            "items.backlinks_spam_score",
            "items.broken_backlinks",
            "items.broken_pages",
            "items.first_seen",
            "items.rank",
            "items.referring_domains",
            "items.referring_domains_nofollow",
            "items.referring_ips",
            "items.referring_links_attributes",
            "items.referring_links_countries",
            "items.referring_links_platform_types",
            "items.referring_links_semantic_locations",
            "items.referring_links_tld",
            "items.referring_links_types.alternate",
            "items.referring_links_types.anchor",
            "items.referring_links_types.canonical",
            "items.referring_links_types.image",
            "items.referring_links_types.redirect",
            "items.referring_main_domains",
            "items.referring_main_domains_nofollow",
            "items.referring_pages",
            "items.referring_pages_nofollow",
            "items.referring_subnets",
            "items.type",
            "items.url",
            "status_code",
            "status_message"
        ],
        "backlinks_domain_pages": [
            "id",
            "items.content_encoding",
            "items.domain",
            "items.encoded_size",
            "items.fetch_time",
            "items.first_visited",
            "items.ip",
            "items.main_domain",
            "items.media_type",
            "items.meta.charset",
            "items.meta.external_links_count",
            "items.meta.h1",
            "items.meta.page_spam_score",
            "items.meta.platform_type",
            "items.meta.title",
            "items.meta.words_count",
            "items.page",
            "items.page_summary.backlinks",
            "items.page_summary.backlinks_spam_score",
            "items.page_summary.broken_backlinks",
            "items.page_summary.broken_pages",
            "items.page_summary.first_seen",
            "items.page_summary.rank",
            "items.page_summary.referring_domains",
            "items.page_summary.referring_domains_nofollow",
            "items.page_summary.referring_ips",
            "items.page_summary.referring_links_attributes",
            "items.page_summary.referring_links_countries",
            "items.page_summary.referring_links_platform_types",
            "items.page_summary.referring_links_semantic_locations",
            "items.page_summary.referring_links_tld",
            "items.page_summary.referring_links_types.alternate",
            "items.page_summary.referring_links_types.anchor",
            "items.page_summary.referring_links_types.canonical",
            "items.page_summary.referring_links_types.image",
            "items.page_summary.referring_links_types.redirect",
            "items.page_summary.referring_main_domains",
            "items.page_summary.referring_main_domains_nofollow",
            "items.page_summary.referring_pages",
            "items.page_summary.referring_pages_nofollow",
            "items.page_summary.referring_subnets",
            "items.prev_visited",
            "items.server",
            "items.size",
            "items.status_code",
            "items.tld",
            "items.type",
            "status_code",
            "status_message"
        ],
        "backlinks_page_intersection": [
            "id",
            "items.page_intersection.*.alt",
            "items.page_intersection.*.anchor",
            "items.page_intersection.*.attributes",
            "items.page_intersection.*.backlink_spam_score",
            "items.page_intersection.*.dofollow",
            "items.page_intersection.*.domain_from",
            "items.page_intersection.*.domain_from_country",
            "items.page_intersection.*.domain_from_ip",
            "items.page_intersection.*.domain_from_platform_type",
            "items.page_intersection.*.domain_from_rank",
            "items.page_intersection.*.domain_to",
            "items.page_intersection.*.first_seen",
            "items.page_intersection.*.is_broken",
            "items.page_intersection.*.is_new",
            "items.page_intersection.*.item_type",
            "items.page_intersection.*.last_seen",
            "items.page_intersection.*.links_count",
            "items.page_intersection.*.original",
            "items.page_intersection.*.page_from_encoding",
            "items.page_intersection.*.page_from_external_links",
            "items.page_intersection.*.page_from_internal_links",
            "items.page_intersection.*.page_from_language",
            "items.page_intersection.*.page_from_rank",
            "items.page_intersection.*.page_from_size",
            "items.page_intersection.*.page_from_status_code",
            "items.page_intersection.*.page_from_title",
            "items.page_intersection.*.prev_seen",
            "items.page_intersection.*.rank",
            "items.page_intersection.*.semantic_location",
            "items.page_intersection.*.text_post",
            "items.page_intersection.*.text_pre",
            "items.page_intersection.*.tld_from",
            "items.page_intersection.*.type",
            "items.page_intersection.*.url_from",
            "items.page_intersection.*.url_from_https",
            "items.page_intersection.*.url_to",
            "items.page_intersection.*.url_to_https",
            "items.page_intersection.*.url_to_spam_score",
            "items.page_intersection.*.url_to_status_code",
            "items.summary.intersections_count",
            "status_code",
            "status_message"
        ],
        "backlinks_referring_domains": [
            "id",
            "items.backlinks",
            "items.backlinks_spam_score",
            "items.broken_backlinks",
            "items.broken_pages",
            "items.domain",
            "items.first_seen",
            "items.rank",
            "items.referring_domains",
            "items.referring_ips",
            "items.referring_links_attributes",
            "items.referring_links_countries",
            "items.referring_links_platform_types",
            "items.referring_links_semantic_locations",
            "items.referring_links_tld",
            "items.referring_links_types.alternate",
            "items.referring_links_types.anchor",
            "items.referring_links_types.image",
            "items.referring_main_domains",
            "items.referring_pages",
            "items.referring_pages_nofollow",
            "items.referring_subnets",
            "items.type",
            "status_code",
            "status_message"
        ],
        "backlinks_referring_networks": [
            "id",
            "items.backlinks",
            "items.broken_backlinks",
            "items.broken_pages",
            "items.first_seen",
            "items.network_address",
            "items.rank",
            "items.referring_domains",
            "items.referring_ips",
            "items.referring_links_attributes",
            "items.referring_links_countries",
            "items.referring_links_platform_types",
            "items.referring_links_semantic_locations",
            "items.referring_links_tld",
            "items.referring_links_types.alternate",
            "items.referring_links_types.anchor",
            "items.referring_links_types.image",
            "items.referring_main_domains",
            "items.referring_pages",
            "items.referring_pages_nofollow",
            "items.referring_subnets",
            "items.type",
            "status_code",
            "status_message"
        ],
        "backlinks_summary": [
            "id",
            "items.backlinks",
            "items.backlinks_spam_score",
            "items.broken_backlinks",
            "items.broken_pages",
            "items.crawled_pages",
            "items.external_links_count",
            "items.first_seen",
            "items.info.country",
            "items.info.ip_address",
            "items.info.platform_type",
            "items.info.server",
            "items.info.target_spam_score",
            "items.rank",
            "items.referring_domains",
            "items.referring_domains_nofollow",
            "items.referring_ips",
            "items.referring_links_attributes",
            "items.referring_links_countries",
            "items.referring_links_platform_types",
            "items.referring_links_semantic_locations",
            "items.referring_links_tld",
            "items.referring_links_types.alternate",
            "items.referring_links_types.anchor",
            "items.referring_links_types.canonical",
            "items.referring_links_types.image",
            "items.referring_links_types.redirect",
            "items.referring_main_domains",
            "items.referring_main_domains_nofollow",
            "items.referring_pages",
            "items.referring_pages_nofollow",
            "items.referring_subnets",
            "items.target",
            "status_code",
            "status_message"
        ],
        "backlinks_timeseries_new_lost_summary": [
            "id",
            "items.date",
            "items.lost_backlinks",
            "items.lost_referring_domains",
            "items.lost_referring_main_domains",
            "items.new_backlinks",
            "items.new_referring_domains",
            "items.new_referring_main_domains",
            "items.type",
            "status_code",
            "status_message"
        ],
        "backlinks_timeseries_summary": [
            "id",
            "items.backlinks",
            "items.backlinks_nofollow",
            "items.date",
            "items.rank",
            "items.referring_domains",
            "items.referring_domains_nofollow",
            "items.referring_ips",
            "items.referring_main_domains",
            "items.referring_main_domains_nofollow",
            "items.referring_pages",
            "items.referring_pages_nofollow",
            "items.referring_subnets",
            "items.type",
            "status_code",
            "status_message"
        ],
        "business_data_business_listings_search": [
            "id",
            "items.additional_categories",
            "items.address",
            "items.address_info.address",
            "items.address_info.borough",
            "items.address_info.city",
            "items.address_info.country_code",
            "items.address_info.region",
            "items.address_info.zip",
            "items.attributes.available_attributes",
            "items.category",
            "items.category_ids",
            "items.check_url",
            "items.cid",
            "items.contact_info.source",
            "items.contact_info.type",
            "items.contact_info.value",
            "items.description",
            "items.domain",
            "items.feature_id",
            "items.first_seen",
            "items.is_claimed",
            "items.last_updated_time",
            "items.latitude",
            "items.logo",
            "items.longitude",
            "items.main_image",
            "items.original_title",
            "items.people_also_search.cid",
            "items.people_also_search.feature_id",
            "items.people_also_search.rating.rating_type",
            "items.people_also_search.rating.value",
            "items.people_also_search.rating.votes_count",
            "items.people_also_search.title",
            "items.phone",
            "items.place_id",
            "items.place_topics",
            "items.popular_times.popular_times_by_days",
            "items.rating.rating_type",
            "items.rating.value",
            "items.rating.votes_count",
            "items.rating_distribution",
            "items.snippet",
            "items.title",
            "items.total_photos",
            "items.type",
            "items.url",
            "items.work_time.work_hours",
            "status_code",
            "status_message"
        ],
        "domain_analytics_whois_overview": [
            "id",
            "items.backlinks_info.backlinks",
            "items.backlinks_info.dofollow",
            "items.backlinks_info.referring_domains",
            "items.backlinks_info.referring_main_domains",
            "items.backlinks_info.referring_pages",
            "items.backlinks_info.time_update",
            "items.changed_datetime",
            "items.created_datetime",
            "items.domain",
            "items.epp_status_codes",
            "items.expiration_datetime",
            "items.first_seen",
            "items.metrics.*.count",
            "items.metrics.*.estimated_paid_traffic_cost",
            "items.metrics.*.etv",
            "items.metrics.*.pos_1",
            "items.metrics.*.pos_11_20",
            "items.metrics.*.pos_21_30",
            "items.metrics.*.pos_2_3",
            "items.metrics.*.pos_31_40",
            "items.metrics.*.pos_41_50",
            "items.metrics.*.pos_4_10",
            "items.metrics.*.pos_51_60",
            "items.metrics.*.pos_61_70",
            "items.metrics.*.pos_71_80",
            "items.metrics.*.pos_81_90",
            "items.metrics.*.pos_91_100",
            "items.registered",
            "items.registrar",
            "items.tld",
            "items.updated_datetime",
            "status_code",
            "status_message"
        ],
        "domain_analytics_whois_available_filters": [],
        "domain_analytics_technologies_domain_technologies": [
            "id",
            "items.content_language_code",
            "items.country_iso_code",
            "items.domain",
            "items.domain_rank",
            "items.last_visited",
            "items.technologies",
            "items.title",
            "items.type",
            "status_code",
            "status_message"
        ],
        "domain_analytics_technologies_available_filters": [],
        "content_analysis_search": [
            "id",
            "items.content_info.author",
            "items.content_info.connotation_types",
            "items.content_info.content_quality_score",
            "items.content_info.content_type",
            "items.content_info.date_published",
            "items.content_info.group_date",
            "items.content_info.language",
            "items.content_info.level",
            "items.content_info.main_title",
            "items.content_info.previous_title",
            "items.content_info.semantic_location",
            "items.content_info.sentiment_connotations",
            "items.content_info.snippet",
            "items.content_info.snippet_length",
            "items.content_info.text_category",
            "items.content_info.title",
            "items.country",
            "items.domain",
            "items.domain_rank",
            "items.fetch_time",
            "items.language",
            "items.main_domain",
            "items.page_category",
            "items.page_types",
            "items.score",
            "items.type",
            "items.url",
            "items.url_rank",
            "status_code",
            "status_message"
        ],
        "content_analysis_summary": [
            "id",
            "items.connotation_types",
            "items.countries",
            "items.languages",
            "items.page_categories.category",
            "items.page_categories.count",
            "items.page_types",
            "items.rank",
            "items.sentiment_connotations",
            "items.text_categories.category",
            "items.text_categories.count",
            "items.top_domains.count",
            "items.top_domains.domain",
            "items.total_count",
            "items.type",
            "status_code",
            "status_message"
        ],
        "content_analysis_phrase_trends": [
            "id",
            "items.connotation_types",
            "items.countries",
            "items.date",
            "items.languages",
            "items.page_categories.category",
            "items.page_categories.count",
            "items.page_types",
            "items.rank",
            "items.sentiment_connotations",
            "items.text_categories.category",
            "items.text_categories.count",
            "items.top_domains.count",
            "items.top_domains.domain",
            "items.total_count",
            "items.type",
            "status_code",
            "status_message"
        ]
    }
}
```
Page 2/3FirstPrevNextLast