Class: Seo::AhrefsMcpClient

Inherits:
Object
  • Object
show all
Defined in:
app/services/seo/ahrefs_mcp_client.rb

Overview

Direct MCP client for Ahrefs API using raw HTTP JSON-RPC.
Implements the MCP (Model Context Protocol) spec for tool discovery and execution.

Examples:

Basic usage

client = Seo::AhrefsMcpClient.new
tools = client.list_tools
result = client.top_pages(target: 'example.com', limit: 10)

Call any tool

result = client.call_tool('site-explorer-organic-keywords', target: url, limit: 50)

See Also:

Defined Under Namespace

Classes: AuthenticationError, Error, ToolNotFoundError

Constant Summary collapse

MCP_URL =
'https://api.ahrefs.com/mcp/mcp'
PROTOCOL_VERSION =
'2025-06-18'

Instance Method Summary collapse

Constructor Details

#initialize(token: nil) ⇒ AhrefsMcpClient

Returns a new instance of AhrefsMcpClient.



28
29
30
31
32
33
34
35
# File 'app/services/seo/ahrefs_mcp_client.rb', line 28

def initialize(token: nil)
  @token = token || Heatwave::Configuration.fetch(:ahrefs, :mcp_token)
  raise AuthenticationError, 'Ahrefs MCP token not configured' if @token.blank?

  @session_id = nil
  @request_id = 0
  @tools_cache = nil
end

Instance Method Details

#call_tool(tool_name, **arguments) ⇒ Hash

Call an MCP tool

Parameters:

  • tool_name (String)

    Name of the tool to call

  • arguments (Hash)

    Tool arguments

Returns:

  • (Hash)

    Tool result



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'app/services/seo/ahrefs_mcp_client.rb', line 66

def call_tool(tool_name, **arguments)
  ensure_session!

  response = send_request('tools/call', {
    name: tool_name,
    arguments: arguments
  })

  result = response['result']
  return nil unless result

  # Extract content from MCP response
  content = result['content']
  return result unless content.is_a?(Array)

  # Parse JSON from text content
  text_content = content.find { |c| c['type'] == 'text' }
  return result unless text_content

  JSON.parse(text_content['text'])
rescue JSON::ParserError
  text_content['text']
end

#closeObject

Close the session



148
149
150
151
# File 'app/services/seo/ahrefs_mcp_client.rb', line 148

def close
  @session_id = nil
  @tools_cache = nil
end

#domain_metrics(target:) ⇒ Hash

Get domain metrics (Domain Rating, etc.)

Parameters:

  • target (String)

    Domain to analyze

Returns:

  • (Hash)

    Domain metrics



134
135
136
137
138
# File 'app/services/seo/ahrefs_mcp_client.rb', line 134

def domain_metrics(target:)
  call_tool('site-explorer-metrics',
            target: target,
            mode: 'subdomains')
end

#initialize_sessionHash

Initialize the MCP session

Returns:

  • (Hash)

    Server info and capabilities



39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/services/seo/ahrefs_mcp_client.rb', line 39

def initialize_session
  response = send_request('initialize', {
    protocolVersion: PROTOCOL_VERSION,
    capabilities: {},
    clientInfo: { name: 'heatwave-seo', version: '1.0.0' }
  })

  # Send initialized notification
  send_notification('notifications/initialized')

  response['result']
end

#list_toolsArray<Hash>

List available tools

Returns:

  • (Array<Hash>)

    Array of tool definitions



54
55
56
57
58
59
60
# File 'app/services/seo/ahrefs_mcp_client.rb', line 54

def list_tools
  @tools_cache ||= begin
    ensure_session!
    response = send_request('tools/list')
    response.dig('result', 'tools') || []
  end
end

#organic_keywords(target:, limit: 50, date: nil, select: nil) ⇒ Hash

Get organic keywords for a URL
Available columns: keyword, best_position, best_position_url, volume, sum_traffic, cpc,
keyword_difficulty, serp_features, best_position_kind, words, etc.

Parameters:

  • target (String)

    URL to analyze (full URL)

  • limit (Integer) (defaults to: 50)

    Max keywords to return (default 50)

  • date (String) (defaults to: nil)

    Date for metrics (default: yesterday)

  • select (String) (defaults to: nil)

    Columns to return

Returns:

  • (Hash)

    Keywords data



118
119
120
121
122
123
124
125
126
127
128
129
# File 'app/services/seo/ahrefs_mcp_client.rb', line 118

def organic_keywords(target:, limit: 50, date: nil, select: nil)
  date ||= Date.yesterday.strftime('%Y-%m-%d')
  # Note: Ahrefs uses 'best_position' not 'position', 'sum_traffic' for estimated traffic
  select ||= 'keyword,best_position,sum_traffic,volume,cpc,keyword_difficulty,serp_features,best_position_kind'

  call_tool('site-explorer-organic-keywords',
            target: target,
            mode: 'exact',
            date: date,
            select: select,
            limit: limit)
end

#tool_doc(tool_name) ⇒ String

Get documentation for a tool

Parameters:

  • tool_name (String)

    Tool name

Returns:

  • (String)

    Tool documentation



143
144
145
# File 'app/services/seo/ahrefs_mcp_client.rb', line 143

def tool_doc(tool_name)
  call_tool('doc', tool: tool_name)
end

#top_pages(target:, limit: 100, date: nil, select: nil) ⇒ Hash

Get top pages by organic traffic

Parameters:

  • target (String)

    Domain to analyze

  • limit (Integer) (defaults to: 100)

    Max pages to return (default 100)

  • date (String) (defaults to: nil)

    Date for metrics (default: yesterday, format: YYYY-MM-DD)

  • select (String) (defaults to: nil)

    Columns to return

Returns:

  • (Hash)

    Top pages data with 'pages' array



96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/services/seo/ahrefs_mcp_client.rb', line 96

def top_pages(target:, limit: 100, date: nil, select: nil)
  date ||= Date.yesterday.strftime('%Y-%m-%d')
  # Default columns: URL, traffic, keywords, top keyword, position, value (in cents)
  select ||= 'url,sum_traffic,keywords,top_keyword,top_keyword_best_position,value'

  call_tool('site-explorer-top-pages',
            target: target,
            mode: 'subdomains',
            date: date,
            select: select,
            limit: limit)
end