ferrum-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.env.example +90 -0
- data/CHANGELOG.md +229 -0
- data/CONTRIBUTING.md +469 -0
- data/LICENSE +21 -0
- data/README.md +334 -0
- data/SECURITY.md +286 -0
- data/bin/ferrum-mcp +66 -0
- data/bin/lint +10 -0
- data/bin/serve +3 -0
- data/bin/test +4 -0
- data/docs/API_REFERENCE.md +1410 -0
- data/docs/CONFIGURATION.md +254 -0
- data/docs/DEPLOYMENT.md +846 -0
- data/docs/DOCKER.md +836 -0
- data/docs/DOCKER_BOTBROWSER.md +455 -0
- data/docs/GETTING_STARTED.md +249 -0
- data/docs/TROUBLESHOOTING.md +677 -0
- data/lib/ferrum_mcp/browser_manager.rb +101 -0
- data/lib/ferrum_mcp/cli/command_handler.rb +99 -0
- data/lib/ferrum_mcp/cli/server_runner.rb +166 -0
- data/lib/ferrum_mcp/configuration.rb +229 -0
- data/lib/ferrum_mcp/resource_manager.rb +223 -0
- data/lib/ferrum_mcp/server.rb +254 -0
- data/lib/ferrum_mcp/session.rb +227 -0
- data/lib/ferrum_mcp/session_manager.rb +183 -0
- data/lib/ferrum_mcp/tools/accept_cookies_tool.rb +458 -0
- data/lib/ferrum_mcp/tools/base_tool.rb +114 -0
- data/lib/ferrum_mcp/tools/clear_cookies_tool.rb +66 -0
- data/lib/ferrum_mcp/tools/click_tool.rb +218 -0
- data/lib/ferrum_mcp/tools/close_session_tool.rb +49 -0
- data/lib/ferrum_mcp/tools/create_session_tool.rb +146 -0
- data/lib/ferrum_mcp/tools/drag_and_drop_tool.rb +171 -0
- data/lib/ferrum_mcp/tools/evaluate_js_tool.rb +46 -0
- data/lib/ferrum_mcp/tools/execute_script_tool.rb +48 -0
- data/lib/ferrum_mcp/tools/fill_form_tool.rb +78 -0
- data/lib/ferrum_mcp/tools/find_by_text_tool.rb +153 -0
- data/lib/ferrum_mcp/tools/get_attribute_tool.rb +56 -0
- data/lib/ferrum_mcp/tools/get_cookies_tool.rb +70 -0
- data/lib/ferrum_mcp/tools/get_html_tool.rb +52 -0
- data/lib/ferrum_mcp/tools/get_session_info_tool.rb +40 -0
- data/lib/ferrum_mcp/tools/get_text_tool.rb +67 -0
- data/lib/ferrum_mcp/tools/get_title_tool.rb +42 -0
- data/lib/ferrum_mcp/tools/get_url_tool.rb +39 -0
- data/lib/ferrum_mcp/tools/go_back_tool.rb +49 -0
- data/lib/ferrum_mcp/tools/go_forward_tool.rb +49 -0
- data/lib/ferrum_mcp/tools/hover_tool.rb +76 -0
- data/lib/ferrum_mcp/tools/list_sessions_tool.rb +33 -0
- data/lib/ferrum_mcp/tools/navigate_tool.rb +59 -0
- data/lib/ferrum_mcp/tools/press_key_tool.rb +91 -0
- data/lib/ferrum_mcp/tools/query_shadow_dom_tool.rb +225 -0
- data/lib/ferrum_mcp/tools/refresh_tool.rb +49 -0
- data/lib/ferrum_mcp/tools/screenshot_tool.rb +121 -0
- data/lib/ferrum_mcp/tools/session_tool.rb +37 -0
- data/lib/ferrum_mcp/tools/set_cookie_tool.rb +77 -0
- data/lib/ferrum_mcp/tools/solve_captcha_tool.rb +528 -0
- data/lib/ferrum_mcp/transport/http_server.rb +93 -0
- data/lib/ferrum_mcp/transport/rate_limiter.rb +79 -0
- data/lib/ferrum_mcp/transport/stdio_server.rb +63 -0
- data/lib/ferrum_mcp/version.rb +5 -0
- data/lib/ferrum_mcp/whisper_service.rb +222 -0
- data/lib/ferrum_mcp.rb +35 -0
- metadata +248 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module FerrumMCP
|
|
6
|
+
module Transport
|
|
7
|
+
# STDIO Server with MCP StdioTransport
|
|
8
|
+
class StdioServer
|
|
9
|
+
attr_reader :server, :config, :logger, :mcp_transport
|
|
10
|
+
|
|
11
|
+
def initialize(server, config)
|
|
12
|
+
@server = server
|
|
13
|
+
@config = config
|
|
14
|
+
@logger = config.logger
|
|
15
|
+
@mcp_transport = MCP::Server::Transports::StdioTransport.new(server.mcp_server)
|
|
16
|
+
server.mcp_server.transport = @mcp_transport
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def start # rubocop:disable Metrics/AbcSize
|
|
20
|
+
logger.info 'Starting STDIO server'
|
|
21
|
+
logger.info 'Reading from STDIN and writing to STDOUT'
|
|
22
|
+
|
|
23
|
+
# Open the transport for stdio communication
|
|
24
|
+
@mcp_transport.open
|
|
25
|
+
|
|
26
|
+
# Read from stdin and process messages
|
|
27
|
+
loop do
|
|
28
|
+
line = $stdin.gets
|
|
29
|
+
break if line.nil? # EOF
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
request = JSON.parse(line.strip)
|
|
33
|
+
response = @mcp_transport.handle_json_request(request)
|
|
34
|
+
$stdout.puts(response.to_json)
|
|
35
|
+
$stdout.flush
|
|
36
|
+
rescue JSON::ParserError => e
|
|
37
|
+
logger.error "Invalid JSON: #{e.message}"
|
|
38
|
+
error_response = {
|
|
39
|
+
jsonrpc: '2.0',
|
|
40
|
+
error: { code: -32_700, message: 'Parse error' },
|
|
41
|
+
id: nil
|
|
42
|
+
}
|
|
43
|
+
$stdout.puts(error_response.to_json)
|
|
44
|
+
$stdout.flush
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
logger.error "Request error: #{e.message}"
|
|
47
|
+
logger.error e.backtrace.join("\n")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
logger.error "STDIO server error: #{e.message}"
|
|
52
|
+
logger.error e.backtrace.join("\n")
|
|
53
|
+
raise
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def stop
|
|
57
|
+
logger.info 'Stopping STDIO server...'
|
|
58
|
+
@mcp_transport&.close
|
|
59
|
+
logger.info 'STDIO server stopped'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tempfile'
|
|
4
|
+
require 'open3'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'net/http'
|
|
7
|
+
require 'uri'
|
|
8
|
+
require 'shellwords'
|
|
9
|
+
|
|
10
|
+
module FerrumMCP
|
|
11
|
+
# Service to handle Whisper speech recognition for CAPTCHA solving
|
|
12
|
+
# Uses whisper-cli (whisper.cpp) for fast, efficient transcription
|
|
13
|
+
class WhisperService
|
|
14
|
+
attr_reader :whisper_path, :model, :language, :logger
|
|
15
|
+
|
|
16
|
+
# Model URLs for whisper.cpp
|
|
17
|
+
MODEL_URLS = {
|
|
18
|
+
'tiny' => 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin',
|
|
19
|
+
'base' => 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin',
|
|
20
|
+
'small' => 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin',
|
|
21
|
+
'medium' => 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin'
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
def initialize(model: nil, language: nil, logger: nil)
|
|
25
|
+
@whisper_path = ENV.fetch('WHISPER_PATH', 'whisper-cli')
|
|
26
|
+
@model = model || ENV.fetch('WHISPER_MODEL', 'base')
|
|
27
|
+
@language = language || ENV.fetch('WHISPER_LANGUAGE', 'en')
|
|
28
|
+
@logger = logger || Logger.new($stdout)
|
|
29
|
+
|
|
30
|
+
verify_whisper_available!
|
|
31
|
+
ensure_model_available!
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Transcribe audio file to text
|
|
35
|
+
# @param audio_path [String] Path to audio file
|
|
36
|
+
# @return [String] Transcribed and cleaned text
|
|
37
|
+
def transcribe(audio_path)
|
|
38
|
+
logger.debug "Transcribing audio: #{audio_path}"
|
|
39
|
+
|
|
40
|
+
cmd = build_whisper_command(audio_path)
|
|
41
|
+
logger.debug "Command: #{cmd.join(' ')}"
|
|
42
|
+
|
|
43
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
44
|
+
|
|
45
|
+
unless status.success?
|
|
46
|
+
logger.error "Whisper failed: #{stderr}"
|
|
47
|
+
raise ToolError, "Whisper transcription failed: #{stderr}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
logger.debug "Whisper stdout: #{stdout}"
|
|
51
|
+
|
|
52
|
+
# Extract transcription from output or file
|
|
53
|
+
transcription = extract_transcription_from_output(stdout) || read_transcription_file(audio_path)
|
|
54
|
+
clean_transcription(transcription)
|
|
55
|
+
ensure
|
|
56
|
+
# Cleanup .txt file created by whisper-cli
|
|
57
|
+
txt_file = "#{audio_path}.txt"
|
|
58
|
+
FileUtils.rm_f(txt_file)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Download audio from URL using browser context
|
|
62
|
+
# @param browser [Ferrum::Browser] Browser instance
|
|
63
|
+
# @param url [String] Audio URL
|
|
64
|
+
# @return [Tempfile] Temporary audio file
|
|
65
|
+
def download_audio(_browser, url)
|
|
66
|
+
temp_file = Tempfile.new(['captcha_audio', '.mp3'])
|
|
67
|
+
temp_file.binmode
|
|
68
|
+
|
|
69
|
+
logger.info "Downloading audio from: #{url[0..80]}..."
|
|
70
|
+
|
|
71
|
+
# Use curl to download (simpler and more reliable)
|
|
72
|
+
_, stderr, status = Open3.capture3("curl -s -L -o #{temp_file.path} #{url.shellescape}")
|
|
73
|
+
|
|
74
|
+
unless status.success?
|
|
75
|
+
logger.error "curl failed: #{stderr}"
|
|
76
|
+
raise ToolError, "Failed to download audio with curl: #{stderr}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Verify file was downloaded
|
|
80
|
+
unless File.exist?(temp_file.path) && File.size(temp_file.path).positive?
|
|
81
|
+
raise ToolError, 'Audio file is empty or not downloaded'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
logger.info "Downloaded: #{File.size(temp_file.path)} bytes"
|
|
85
|
+
temp_file
|
|
86
|
+
rescue StandardError => e
|
|
87
|
+
temp_file&.close
|
|
88
|
+
temp_file&.unlink
|
|
89
|
+
raise ToolError, "Failed to download audio: #{e.message}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Check if Whisper is available
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
def available?
|
|
95
|
+
verify_whisper_available!
|
|
96
|
+
true
|
|
97
|
+
rescue StandardError
|
|
98
|
+
false
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def verify_whisper_available!
|
|
104
|
+
_, stderr, status = Open3.capture3("#{whisper_path} --help 2>&1")
|
|
105
|
+
|
|
106
|
+
unless status.success?
|
|
107
|
+
raise ToolError,
|
|
108
|
+
"Whisper not found at '#{whisper_path}'. " \
|
|
109
|
+
"Install with: brew install whisper-cpp\n" \
|
|
110
|
+
"Or on Linux: follow instructions at https://github.com/ggerganov/whisper.cpp\n" \
|
|
111
|
+
"Or set WHISPER_PATH environment variable.\n" \
|
|
112
|
+
"Error: #{stderr}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
logger.info "Whisper CLI available at: #{whisper_path}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def ensure_model_available!
|
|
119
|
+
model_path = get_model_path
|
|
120
|
+
|
|
121
|
+
# Check if model already exists
|
|
122
|
+
if File.exist?(model_path)
|
|
123
|
+
logger.debug "Model already available: #{model_path}"
|
|
124
|
+
return
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Download model
|
|
128
|
+
logger.info "Model '#{model}' not found, downloading..."
|
|
129
|
+
download_model(model_path)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def get_model_path # rubocop:disable Naming/AccessorMethodName
|
|
133
|
+
models_dir = File.expand_path('~/.whisper.cpp/models')
|
|
134
|
+
FileUtils.mkdir_p(models_dir)
|
|
135
|
+
File.join(models_dir, "ggml-#{model}.bin")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def download_model(model_path) # rubocop:disable Metrics/AbcSize
|
|
139
|
+
url = MODEL_URLS[model]
|
|
140
|
+
|
|
141
|
+
raise ToolError, "Unknown model: #{model}. Available: #{MODEL_URLS.keys.join(', ')}" unless url
|
|
142
|
+
|
|
143
|
+
logger.info "Downloading model from: #{url}"
|
|
144
|
+
logger.info 'This may take a few minutes...'
|
|
145
|
+
|
|
146
|
+
# Download with progress
|
|
147
|
+
uri = URI(url)
|
|
148
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
149
|
+
request = Net::HTTP::Get.new(uri)
|
|
150
|
+
|
|
151
|
+
http.request(request) do |response|
|
|
152
|
+
raise ToolError, "Failed to download model: HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
153
|
+
|
|
154
|
+
total_size = response['content-length'].to_i
|
|
155
|
+
downloaded = 0
|
|
156
|
+
|
|
157
|
+
File.open(model_path, 'wb') do |file|
|
|
158
|
+
response.read_body do |chunk|
|
|
159
|
+
file.write(chunk)
|
|
160
|
+
downloaded += chunk.size
|
|
161
|
+
|
|
162
|
+
# Log progress every 10MB
|
|
163
|
+
if (downloaded % (10 * 1024 * 1024)).zero? || downloaded == total_size
|
|
164
|
+
progress = (downloaded * 100.0 / total_size).round(1)
|
|
165
|
+
mb_downloaded = downloaded / 1024 / 1024
|
|
166
|
+
mb_total = total_size / 1024 / 1024
|
|
167
|
+
logger.info "Download progress: #{progress}% (#{mb_downloaded}MB / #{mb_total}MB)"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
logger.info "Model downloaded successfully: #{model_path}"
|
|
175
|
+
rescue StandardError => e
|
|
176
|
+
FileUtils.rm_f(model_path)
|
|
177
|
+
raise ToolError, "Failed to download model: #{e.message}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def build_whisper_command(audio_path)
|
|
181
|
+
model_path = get_model_path
|
|
182
|
+
|
|
183
|
+
[
|
|
184
|
+
whisper_path,
|
|
185
|
+
'--model', model_path,
|
|
186
|
+
'--language', language,
|
|
187
|
+
'--output-txt',
|
|
188
|
+
'--file', audio_path
|
|
189
|
+
]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def extract_transcription_from_output(output)
|
|
193
|
+
# whisper-cli outputs the transcription in the stdout
|
|
194
|
+
# Format: [00:00:00.000 --> 00:00:05.000] transcription text here
|
|
195
|
+
lines = output.lines.grep(/\]\s+\w/)
|
|
196
|
+
return nil if lines.empty?
|
|
197
|
+
|
|
198
|
+
# Extract text after the timestamp
|
|
199
|
+
transcription = lines.map do |line|
|
|
200
|
+
line.sub(/\[.*?\]\s*/, '').strip
|
|
201
|
+
end.join(' ')
|
|
202
|
+
|
|
203
|
+
transcription.empty? ? nil : transcription
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def read_transcription_file(audio_path)
|
|
207
|
+
# whisper-cli creates audio_path.txt in the same directory
|
|
208
|
+
txt_file = "#{audio_path}.txt"
|
|
209
|
+
|
|
210
|
+
raise ToolError, "Whisper output file not found: #{txt_file}" unless File.exist?(txt_file)
|
|
211
|
+
|
|
212
|
+
File.read(txt_file).strip
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def clean_transcription(text)
|
|
216
|
+
text.strip
|
|
217
|
+
.gsub(/\s+/, ' ') # normalize whitespace
|
|
218
|
+
.gsub(/[^\w\s]/, '') # remove punctuation
|
|
219
|
+
.downcase # lowercase for consistency
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
data/lib/ferrum_mcp.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
require 'ferrum'
|
|
5
|
+
require 'logger'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'zeitwerk'
|
|
8
|
+
|
|
9
|
+
# Setup Zeitwerk loader
|
|
10
|
+
loader = Zeitwerk::Loader.for_gem
|
|
11
|
+
|
|
12
|
+
# Custom inflector for acronyms
|
|
13
|
+
loader.inflector.inflect(
|
|
14
|
+
'ferrum_mcp' => 'FerrumMCP',
|
|
15
|
+
'mcp' => 'MCP',
|
|
16
|
+
'cli' => 'CLI',
|
|
17
|
+
'get_html_tool' => 'GetHTMLTool',
|
|
18
|
+
'get_url_tool' => 'GetURLTool',
|
|
19
|
+
'evaluate_js_tool' => 'EvaluateJSTool',
|
|
20
|
+
'http_server' => 'HTTPServer',
|
|
21
|
+
'query_shadow_dom_tool' => 'QueryShadowDOMTool'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
loader.setup
|
|
25
|
+
|
|
26
|
+
# Main module for Ferrum MCP Server
|
|
27
|
+
module FerrumMCP
|
|
28
|
+
class Error < StandardError; end
|
|
29
|
+
class BrowserError < Error; end
|
|
30
|
+
class ToolError < Error; end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Eager load in production, lazy load in development
|
|
34
|
+
# Note: bin/ferrum-mcp loads this file first to avoid circular dependencies
|
|
35
|
+
loader.eager_load unless ENV['RACK_ENV'] == 'development'
|
metadata
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ferrum-mcp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Eth3rnit3
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: dotenv
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: ferrum
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 0.17.1
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 0.17.1
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: json
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.16'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.16'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: logger
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.7'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.7'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: mcp
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 0.4.0
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 0.4.0
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: puma
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '7.1'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '7.1'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rack
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '3.2'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '3.2'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: ruby-vips
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '2.2'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '2.2'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: zeitwerk
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '2.7'
|
|
132
|
+
type: :runtime
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '2.7'
|
|
139
|
+
description: |
|
|
140
|
+
FerrumMCP is a browser automation server that implements the Model Context Protocol (MCP),
|
|
141
|
+
enabling AI assistants to interact with web pages through a standardized interface.
|
|
142
|
+
Features include navigation, form interaction, content extraction, screenshot capture,
|
|
143
|
+
JavaScript execution, cookie management, and advanced capabilities like smart cookie banner
|
|
144
|
+
detection and AI-powered CAPTCHA solving.
|
|
145
|
+
email:
|
|
146
|
+
- eth3rnit3@gmail.com
|
|
147
|
+
executables:
|
|
148
|
+
- ferrum-mcp
|
|
149
|
+
extensions: []
|
|
150
|
+
extra_rdoc_files: []
|
|
151
|
+
files:
|
|
152
|
+
- ".env.example"
|
|
153
|
+
- CHANGELOG.md
|
|
154
|
+
- CONTRIBUTING.md
|
|
155
|
+
- LICENSE
|
|
156
|
+
- README.md
|
|
157
|
+
- SECURITY.md
|
|
158
|
+
- bin/ferrum-mcp
|
|
159
|
+
- bin/lint
|
|
160
|
+
- bin/serve
|
|
161
|
+
- bin/test
|
|
162
|
+
- docs/API_REFERENCE.md
|
|
163
|
+
- docs/CONFIGURATION.md
|
|
164
|
+
- docs/DEPLOYMENT.md
|
|
165
|
+
- docs/DOCKER.md
|
|
166
|
+
- docs/DOCKER_BOTBROWSER.md
|
|
167
|
+
- docs/GETTING_STARTED.md
|
|
168
|
+
- docs/TROUBLESHOOTING.md
|
|
169
|
+
- lib/ferrum_mcp.rb
|
|
170
|
+
- lib/ferrum_mcp/browser_manager.rb
|
|
171
|
+
- lib/ferrum_mcp/cli/command_handler.rb
|
|
172
|
+
- lib/ferrum_mcp/cli/server_runner.rb
|
|
173
|
+
- lib/ferrum_mcp/configuration.rb
|
|
174
|
+
- lib/ferrum_mcp/resource_manager.rb
|
|
175
|
+
- lib/ferrum_mcp/server.rb
|
|
176
|
+
- lib/ferrum_mcp/session.rb
|
|
177
|
+
- lib/ferrum_mcp/session_manager.rb
|
|
178
|
+
- lib/ferrum_mcp/tools/accept_cookies_tool.rb
|
|
179
|
+
- lib/ferrum_mcp/tools/base_tool.rb
|
|
180
|
+
- lib/ferrum_mcp/tools/clear_cookies_tool.rb
|
|
181
|
+
- lib/ferrum_mcp/tools/click_tool.rb
|
|
182
|
+
- lib/ferrum_mcp/tools/close_session_tool.rb
|
|
183
|
+
- lib/ferrum_mcp/tools/create_session_tool.rb
|
|
184
|
+
- lib/ferrum_mcp/tools/drag_and_drop_tool.rb
|
|
185
|
+
- lib/ferrum_mcp/tools/evaluate_js_tool.rb
|
|
186
|
+
- lib/ferrum_mcp/tools/execute_script_tool.rb
|
|
187
|
+
- lib/ferrum_mcp/tools/fill_form_tool.rb
|
|
188
|
+
- lib/ferrum_mcp/tools/find_by_text_tool.rb
|
|
189
|
+
- lib/ferrum_mcp/tools/get_attribute_tool.rb
|
|
190
|
+
- lib/ferrum_mcp/tools/get_cookies_tool.rb
|
|
191
|
+
- lib/ferrum_mcp/tools/get_html_tool.rb
|
|
192
|
+
- lib/ferrum_mcp/tools/get_session_info_tool.rb
|
|
193
|
+
- lib/ferrum_mcp/tools/get_text_tool.rb
|
|
194
|
+
- lib/ferrum_mcp/tools/get_title_tool.rb
|
|
195
|
+
- lib/ferrum_mcp/tools/get_url_tool.rb
|
|
196
|
+
- lib/ferrum_mcp/tools/go_back_tool.rb
|
|
197
|
+
- lib/ferrum_mcp/tools/go_forward_tool.rb
|
|
198
|
+
- lib/ferrum_mcp/tools/hover_tool.rb
|
|
199
|
+
- lib/ferrum_mcp/tools/list_sessions_tool.rb
|
|
200
|
+
- lib/ferrum_mcp/tools/navigate_tool.rb
|
|
201
|
+
- lib/ferrum_mcp/tools/press_key_tool.rb
|
|
202
|
+
- lib/ferrum_mcp/tools/query_shadow_dom_tool.rb
|
|
203
|
+
- lib/ferrum_mcp/tools/refresh_tool.rb
|
|
204
|
+
- lib/ferrum_mcp/tools/screenshot_tool.rb
|
|
205
|
+
- lib/ferrum_mcp/tools/session_tool.rb
|
|
206
|
+
- lib/ferrum_mcp/tools/set_cookie_tool.rb
|
|
207
|
+
- lib/ferrum_mcp/tools/solve_captcha_tool.rb
|
|
208
|
+
- lib/ferrum_mcp/transport/http_server.rb
|
|
209
|
+
- lib/ferrum_mcp/transport/rate_limiter.rb
|
|
210
|
+
- lib/ferrum_mcp/transport/stdio_server.rb
|
|
211
|
+
- lib/ferrum_mcp/version.rb
|
|
212
|
+
- lib/ferrum_mcp/whisper_service.rb
|
|
213
|
+
homepage: https://github.com/Eth3rnit3/FerrumMCP
|
|
214
|
+
licenses:
|
|
215
|
+
- MIT
|
|
216
|
+
metadata:
|
|
217
|
+
homepage_uri: https://github.com/Eth3rnit3/FerrumMCP
|
|
218
|
+
source_code_uri: https://github.com/Eth3rnit3/FerrumMCP
|
|
219
|
+
changelog_uri: https://github.com/Eth3rnit3/FerrumMCP/blob/main/CHANGELOG.md
|
|
220
|
+
bug_tracker_uri: https://github.com/Eth3rnit3/FerrumMCP/issues
|
|
221
|
+
documentation_uri: https://github.com/Eth3rnit3/FerrumMCP/tree/main/docs
|
|
222
|
+
rubygems_mfa_required: 'true'
|
|
223
|
+
post_install_message: "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nThank
|
|
224
|
+
you for installing FerrumMCP 1.0.0!\n\n\U0001F4DA Documentation: https://github.com/Eth3rnit3/FerrumMCP/tree/main/docs\n\U0001F680
|
|
225
|
+
Quick Start: https://github.com/Eth3rnit3/FerrumMCP/blob/main/docs/GETTING_STARTED.md\n\U0001F41B
|
|
226
|
+
Issues: https://github.com/Eth3rnit3/FerrumMCP/issues\n\nTo start the server:\n\n
|
|
227
|
+
\ ferrum-mcp start [OPTIONS]\n\nFor help:\n\n ferrum-mcp --help\n\n⚠️ Requirements:\n
|
|
228
|
+
\ - Chrome/Chromium browser must be installed\n - Optional: whisper-cli for CAPTCHA
|
|
229
|
+
solving\n - Optional: BotBrowser for anti-detection\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
|
230
|
+
rdoc_options: []
|
|
231
|
+
require_paths:
|
|
232
|
+
- lib
|
|
233
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
234
|
+
requirements:
|
|
235
|
+
- - ">="
|
|
236
|
+
- !ruby/object:Gem::Version
|
|
237
|
+
version: 3.2.0
|
|
238
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
239
|
+
requirements:
|
|
240
|
+
- - ">="
|
|
241
|
+
- !ruby/object:Gem::Version
|
|
242
|
+
version: '0'
|
|
243
|
+
requirements: []
|
|
244
|
+
rubygems_version: 3.5.22
|
|
245
|
+
signing_key:
|
|
246
|
+
specification_version: 4
|
|
247
|
+
summary: Browser automation server implementing the Model Context Protocol
|
|
248
|
+
test_files: []
|