searxng 0.1.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/CHANGELOG.md +9 -0
- data/LICENSE +21 -0
- data/README.md +201 -0
- data/bin/searxng +70 -0
- data/lib/searxng/client.rb +142 -0
- data/lib/searxng/errors.rb +18 -0
- data/lib/searxng/server.rb +131 -0
- data/lib/searxng/version.rb +3 -0
- data/lib/searxng.rb +6 -0
- data/sig/searxng.rbs +36 -0
- metadata +329 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2b8849ed3269f41fc39d8b6274f8b0ae92ade2649dd1f6e5111b1799fec375e5
|
|
4
|
+
data.tar.gz: cd78466743cbad58a93d2453f2128e7bfb2f139a05c84eba0c984166da662e2f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 38818edbabaf94b55bca1e43cbee38fb8bf7d54319d1b2fe3b56a10653f33152011697dd01e9135d1e05fc23f07629a716578dce791b55addc97ca3f471afd92
|
|
7
|
+
data.tar.gz: 4a77e1ba98532c75d7da8c5e3da80c5cb53127488c094fa8dfc331de5285afb4a78e5278f332aa92e27d8e489a1273e8e39eea1027d4643988869c9bdd75b610
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial release.
|
|
6
|
+
- `Searxng::Client` for SearXNG `/search` JSON API (results, infoboxes, suggestions, answers, corrections).
|
|
7
|
+
- CLI: `searxng search QUERY` and `searxng serve` (MCP server).
|
|
8
|
+
- MCP tool `searxng_web_search` (query, pageno, time_range, language, safesearch).
|
|
9
|
+
- Configuration via SEARXNG_URL, SEARXNG_USER, SEARXNG_PASSWORD.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Andrei Makarov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# searxng
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/searxng) [](https://github.com/amkisko/searxng.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/searxng.rb)
|
|
4
|
+
|
|
5
|
+
Ruby gem providing a SearXNG HTTP client, CLI (search), and MCP (Model Context Protocol) server for web search. Integrates with MCP-compatible clients like Codex, Cursor, Claude, and other MCP-enabled tools.
|
|
6
|
+
|
|
7
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
8
|
+
|
|
9
|
+
<a href="https://www.kiskolabs.com">
|
|
10
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
11
|
+
</a>
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- **Ruby 3.1 or higher** (Ruby 3.0 and earlier are not supported). For managing Ruby versions, [rbenv](https://github.com/rbenv/rbenv) or [mise](https://mise.jdx.dev/) are recommended; system Ruby may be sufficient if it meets the version requirement.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install searxng
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or add to your Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem "searxng"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Configuration
|
|
30
|
+
|
|
31
|
+
- **SEARXNG_URL** (required for remote): Base URL of your SearXNG instance (e.g. `http://localhost:8080` or `https://search.example.com`). Defaults to `http://localhost:8080` when not set.
|
|
32
|
+
- **SEARXNG_USER** / **SEARXNG_PASSWORD** (optional): Basic auth credentials if your instance is protected.
|
|
33
|
+
- **SEARXNG_CA_FILE** / **SEARXNG_CA_PATH** (optional): Custom CA certificate file or directory for HTTPS. You can also pass `ca_file:`, `ca_path:`, or `verify_mode:` to `Searxng::Client.new`.
|
|
34
|
+
- **SEARXNG_USER_AGENT** (optional): Custom User-Agent string. The client sends a default identifying the gem; if your instance returns 403 Forbidden (e.g. bot detection), set a custom User-Agent or pass `user_agent:` to `Searxng::Client.new`.
|
|
35
|
+
|
|
36
|
+
To fully customize the HTTP client (e.g. custom certs, proxy, timeouts), override `#build_http(uri)` in a subclass, or pass a `configure_http:` callable to the client; it is invoked with the `Net::HTTP` instance and the request URI before each request.
|
|
37
|
+
|
|
38
|
+
### Cursor IDE Configuration
|
|
39
|
+
|
|
40
|
+
For Cursor IDE, create or update `.cursor/mcp.json` in your project:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"searxng": {
|
|
46
|
+
"command": "bundle",
|
|
47
|
+
"args": ["exec", "searxng", "serve"],
|
|
48
|
+
"env": {
|
|
49
|
+
"SEARXNG_URL": "http://localhost:8080"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Note**: Using `gem exec searxng serve` (with `command`: `"gem"`, `args`: `["exec", "searxng", "serve"]`) ensures the correct Ruby version is used when the gem is installed globally.
|
|
57
|
+
|
|
58
|
+
### Claude Desktop Configuration
|
|
59
|
+
|
|
60
|
+
For Claude Desktop, edit the MCP configuration file:
|
|
61
|
+
|
|
62
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
63
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"searxng": {
|
|
69
|
+
"command": "bundle",
|
|
70
|
+
"args": ["exec", "searxng", "serve"],
|
|
71
|
+
"env": {
|
|
72
|
+
"SEARXNG_URL": "http://localhost:8080"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Note**: After updating the configuration, restart Claude Desktop for changes to take effect.
|
|
80
|
+
|
|
81
|
+
### Running the MCP Server manually
|
|
82
|
+
|
|
83
|
+
After installation, you can start the MCP server immediately:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# With bundler
|
|
87
|
+
bundle exec searxng serve
|
|
88
|
+
|
|
89
|
+
# Or if installed globally
|
|
90
|
+
gem exec searxng serve
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The server will start and communicate via STDIN/STDOUT using the MCP protocol.
|
|
94
|
+
|
|
95
|
+
### Testing with MCP Inspector
|
|
96
|
+
|
|
97
|
+
You can test the MCP server using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) tool:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Ensure a SearXNG instance is running (e.g. docker compose -f examples/docker-compose.yml up -d)
|
|
101
|
+
export SEARXNG_URL="http://localhost:8080"
|
|
102
|
+
|
|
103
|
+
# Run the MCP inspector with the server
|
|
104
|
+
npx @modelcontextprotocol/inspector bundle exec searxng serve
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The inspector will:
|
|
108
|
+
|
|
109
|
+
1. Start a proxy server and open a browser interface
|
|
110
|
+
2. Connect to your MCP server via STDIO
|
|
111
|
+
3. Allow you to test all available tools interactively
|
|
112
|
+
4. Display request/response messages and any errors
|
|
113
|
+
|
|
114
|
+
Useful for testing the `searxng_web_search` tool before integrating with Cursor or other MCP clients.
|
|
115
|
+
|
|
116
|
+
## Features
|
|
117
|
+
|
|
118
|
+
- **SearXNG HTTP Client**: Full-featured client for the SearXNG search API (results, infoboxes, suggestions, answers, corrections)
|
|
119
|
+
- **CLI**: `searxng search` for ad-hoc queries and `searxng serve` for the MCP server
|
|
120
|
+
- **MCP Server Integration**: Ready-to-use MCP server with web search tool, compatible with Cursor IDE, Claude Desktop, and other MCP-enabled tools
|
|
121
|
+
- **Configurable HTTP**: Optional custom CA, verify mode, and `configure_http` hook or `build_http` override for proxy/timeouts/certs
|
|
122
|
+
- **Basic Auth**: Optional Basic authentication via options or ENV
|
|
123
|
+
|
|
124
|
+
## Basic Usage
|
|
125
|
+
|
|
126
|
+
### Ruby API
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
require "searxng"
|
|
130
|
+
|
|
131
|
+
client = Searxng::Client.new
|
|
132
|
+
data = client.search("ruby programming", pageno: 1, language: "en")
|
|
133
|
+
|
|
134
|
+
data[:results].each do |r|
|
|
135
|
+
puts r[:title], r[:url], r[:content]
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### CLI
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
searxng search "your query"
|
|
143
|
+
searxng search "ruby" --page 2 --language en --time-range day --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Options: `--url`, `--page`, `--language`, `--time-range` (day|month|year), `--safesearch` (0|1|2), `--json`.
|
|
147
|
+
|
|
148
|
+
## MCP Tools
|
|
149
|
+
|
|
150
|
+
The MCP server provides the following tools:
|
|
151
|
+
|
|
152
|
+
1. **searxng_web_search** - Web search via SearXNG
|
|
153
|
+
- Parameters: `query` (required), `pageno` (optional), `max_results` (optional, default 10), `time_range` (optional), `language` (optional), `safesearch` (optional). Use `max_results` to limit how many results are returned in one response (reduces token usage); use `pageno` for the next page.
|
|
154
|
+
|
|
155
|
+
## Examples
|
|
156
|
+
|
|
157
|
+
See [examples/SETUP.md](examples/SETUP.md) for running SearXNG locally (e.g. Docker) and [examples/run_queries.rb](examples/run_queries.rb) for a small script using the client.
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Install dependencies
|
|
163
|
+
bundle install
|
|
164
|
+
|
|
165
|
+
# Run tests
|
|
166
|
+
bundle exec rspec
|
|
167
|
+
|
|
168
|
+
# Run tests across multiple Ruby versions
|
|
169
|
+
bundle exec appraisal install
|
|
170
|
+
bundle exec appraisal rspec
|
|
171
|
+
|
|
172
|
+
# Run linting
|
|
173
|
+
bundle exec rubocop
|
|
174
|
+
|
|
175
|
+
# Validate RBS type signatures
|
|
176
|
+
bundle exec rbs validate
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/searxng.rb.
|
|
182
|
+
|
|
183
|
+
Contribution policy:
|
|
184
|
+
- New features are not necessarily added to the gem
|
|
185
|
+
- Pull request should have test coverage for affected parts
|
|
186
|
+
- Pull request should have changelog entry
|
|
187
|
+
|
|
188
|
+
Review policy:
|
|
189
|
+
- It might take up to 2 calendar weeks to review and merge critical fixes
|
|
190
|
+
- It might take up to 6 calendar months to review and merge pull request
|
|
191
|
+
- It might take up to 1 calendar year to review an issue
|
|
192
|
+
|
|
193
|
+
For more information, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
194
|
+
|
|
195
|
+
## Security
|
|
196
|
+
|
|
197
|
+
If you discover a security vulnerability, please report it responsibly. See [SECURITY.md](SECURITY.md) for details.
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/searxng
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
cmd = ARGV.shift
|
|
4
|
+
|
|
5
|
+
case cmd
|
|
6
|
+
when "serve"
|
|
7
|
+
unless ENV["DEBUG"]
|
|
8
|
+
require "stringio"
|
|
9
|
+
$stderr = StringIO.new
|
|
10
|
+
$stderr.set_encoding("UTF-8")
|
|
11
|
+
end
|
|
12
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
13
|
+
require "searxng/server"
|
|
14
|
+
Searxng::Server.start
|
|
15
|
+
when "search"
|
|
16
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
17
|
+
require "searxng"
|
|
18
|
+
require "optparse"
|
|
19
|
+
require "json"
|
|
20
|
+
|
|
21
|
+
base_url = nil
|
|
22
|
+
page = 1
|
|
23
|
+
language = "all"
|
|
24
|
+
time_range = nil
|
|
25
|
+
safesearch = nil
|
|
26
|
+
json_output = false
|
|
27
|
+
|
|
28
|
+
OptionParser.new do |o|
|
|
29
|
+
o.on("--url URL", "SearXNG base URL (overrides SEARXNG_URL)") { |v| base_url = v }
|
|
30
|
+
o.on("--page N", Integer, "Page number (default: 1)") { |v| page = v }
|
|
31
|
+
o.on("--language LANG", "Language code (default: all)") { |v| language = v }
|
|
32
|
+
o.on("--time-range RANGE", "Time range: day, month, year") { |v| time_range = v }
|
|
33
|
+
o.on("--safesearch N", Integer, "Safe search: 0, 1, or 2") { |v| safesearch = v }
|
|
34
|
+
o.on("--json", "Output raw JSON") { json_output = true }
|
|
35
|
+
end.parse!(ARGV)
|
|
36
|
+
|
|
37
|
+
query = ARGV.join(" ").strip
|
|
38
|
+
if query.empty?
|
|
39
|
+
warn "Usage: searxng search QUERY [--url URL] [--page N] [--language LANG] [--time-range day|month|year] [--safesearch 0|1|2] [--json]"
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
opts = { pageno: page, language: language, time_range: time_range, safesearch: safesearch }
|
|
44
|
+
opts[:base_url] = base_url if base_url
|
|
45
|
+
|
|
46
|
+
client = Searxng::Client.new(**opts.slice(:base_url))
|
|
47
|
+
data = client.search(query, **opts.slice(:pageno, :time_range, :language, :safesearch))
|
|
48
|
+
|
|
49
|
+
if json_output
|
|
50
|
+
puts JSON.pretty_generate(data)
|
|
51
|
+
else
|
|
52
|
+
data[:infoboxes]&.each do |ib|
|
|
53
|
+
puts "Infobox: #{ib[:infobox]}"
|
|
54
|
+
puts " #{ib[:content]}"
|
|
55
|
+
puts ""
|
|
56
|
+
end
|
|
57
|
+
data[:results]&.each do |r|
|
|
58
|
+
puts "#{r[:title]}"
|
|
59
|
+
puts " #{r[:url]}"
|
|
60
|
+
puts " #{r[:content]}"
|
|
61
|
+
puts ""
|
|
62
|
+
end
|
|
63
|
+
puts "No results found" if (data[:results].nil? || data[:results].empty?) && (data[:infoboxes].nil? || data[:infoboxes].empty?)
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
warn "Usage: searxng (search|serve) ..."
|
|
67
|
+
warn " search QUERY - Run a search and print results"
|
|
68
|
+
warn " serve - Start MCP server (stdio)"
|
|
69
|
+
exit 1
|
|
70
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Searxng
|
|
7
|
+
class Client
|
|
8
|
+
DEFAULT_BASE_URL = "http://localhost:8080"
|
|
9
|
+
DEFAULT_USER_AGENT = "searxng-ruby/#{Searxng::VERSION} (https://github.com/amkisko/searxng.rb)".freeze
|
|
10
|
+
VALID_TIME_RANGES = %w[day month year].freeze
|
|
11
|
+
VALID_SAFESEARCH = [0, 1, 2].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(
|
|
14
|
+
base_url: nil,
|
|
15
|
+
user: nil,
|
|
16
|
+
password: nil,
|
|
17
|
+
open_timeout: 10,
|
|
18
|
+
read_timeout: 10,
|
|
19
|
+
ca_file: nil,
|
|
20
|
+
ca_path: nil,
|
|
21
|
+
verify_mode: nil,
|
|
22
|
+
user_agent: nil,
|
|
23
|
+
configure_http: nil
|
|
24
|
+
)
|
|
25
|
+
@base_url = (base_url || ENV["SEARXNG_URL"] || DEFAULT_BASE_URL).to_s.chomp("/")
|
|
26
|
+
@user = user || ENV["SEARXNG_USER"]
|
|
27
|
+
@password = password || ENV["SEARXNG_PASSWORD"]
|
|
28
|
+
@open_timeout = open_timeout
|
|
29
|
+
@read_timeout = read_timeout
|
|
30
|
+
@ca_file = ca_file || ENV["SEARXNG_CA_FILE"]
|
|
31
|
+
@ca_path = ca_path || ENV["SEARXNG_CA_PATH"]
|
|
32
|
+
@verify_mode = verify_mode
|
|
33
|
+
@user_agent = user_agent || ENV["SEARXNG_USER_AGENT"] || DEFAULT_USER_AGENT
|
|
34
|
+
@configure_http = configure_http
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def search(query, pageno: 1, time_range: nil, language: "all", safesearch: nil)
|
|
38
|
+
raise ConfigurationError, "SEARXNG_URL not set" if @base_url.nil? || @base_url.empty?
|
|
39
|
+
|
|
40
|
+
uri = build_search_uri(query, pageno: pageno, time_range: time_range, language: language, safesearch: safesearch)
|
|
41
|
+
response = perform_request(uri)
|
|
42
|
+
parse_response(response, uri, query)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def build_search_uri(query, pageno:, time_range:, language:, safesearch:)
|
|
48
|
+
uri = URI.join("#{@base_url}/", "search")
|
|
49
|
+
params = {"q" => query, "format" => "json", "pageno" => pageno.to_s}
|
|
50
|
+
params["time_range"] = time_range if time_range && VALID_TIME_RANGES.include?(time_range.to_s)
|
|
51
|
+
params["language"] = language if language && language != "all"
|
|
52
|
+
params["safesearch"] = safesearch.to_s if safesearch && VALID_SAFESEARCH.include?(safesearch.to_i)
|
|
53
|
+
uri.query = URI.encode_www_form(params)
|
|
54
|
+
uri
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def perform_request(uri)
|
|
58
|
+
http = build_http(uri)
|
|
59
|
+
@configure_http&.call(http, uri)
|
|
60
|
+
|
|
61
|
+
request = Net::HTTP::Get.new(uri)
|
|
62
|
+
request["Accept"] = "application/json"
|
|
63
|
+
request["User-Agent"] = @user_agent
|
|
64
|
+
request.basic_auth(@user, @password) if @user && @password
|
|
65
|
+
|
|
66
|
+
http.request(request)
|
|
67
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::EPIPE, Timeout::Error, OpenSSL::SSL::SSLError, EOFError => e
|
|
68
|
+
raise NetworkError, "Connection failed: #{e.message}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Builds and returns a configured Net::HTTP instance. Override in a subclass to customize
|
|
72
|
+
# timeouts, SSL, proxy, or other options. Called once per request.
|
|
73
|
+
def build_http(uri)
|
|
74
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
75
|
+
http.use_ssl = (uri.scheme == "https")
|
|
76
|
+
http.open_timeout = @open_timeout
|
|
77
|
+
http.read_timeout = @read_timeout
|
|
78
|
+
apply_ssl_config(http) if http.use_ssl?
|
|
79
|
+
http
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def apply_ssl_config(http)
|
|
83
|
+
http.verify_mode = @verify_mode || OpenSSL::SSL::VERIFY_PEER
|
|
84
|
+
default_ca = OpenSSL::X509::DEFAULT_CERT_FILE
|
|
85
|
+
http.ca_file = @ca_file if @ca_file && File.exist?(@ca_file)
|
|
86
|
+
http.ca_file = default_ca if !http.ca_file && File.exist?(default_ca)
|
|
87
|
+
http.ca_path = @ca_path if @ca_path && File.directory?(@ca_path)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_response(response, uri, query)
|
|
91
|
+
case response
|
|
92
|
+
when Net::HTTPSuccess
|
|
93
|
+
body = response.body
|
|
94
|
+
data = JSON.parse(body)
|
|
95
|
+
{
|
|
96
|
+
query: data["query"] || query,
|
|
97
|
+
number_of_results: data["number_of_results"],
|
|
98
|
+
results: normalize_results(data["results"] || []),
|
|
99
|
+
infoboxes: normalize_infoboxes(data["infoboxes"] || []),
|
|
100
|
+
suggestions: data["suggestions"] || [],
|
|
101
|
+
answers: data["answers"] || [],
|
|
102
|
+
corrections: data["corrections"] || []
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
raise APIError.new(
|
|
106
|
+
"SearXNG returned #{response.code} #{response.message}",
|
|
107
|
+
status_code: response.code.to_i,
|
|
108
|
+
response_data: response.body,
|
|
109
|
+
uri: uri.to_s
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
rescue JSON::ParserError => e
|
|
113
|
+
raise APIError.new(
|
|
114
|
+
"Invalid JSON response: #{e.message}",
|
|
115
|
+
response_data: response&.body,
|
|
116
|
+
uri: uri.to_s
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def normalize_results(results)
|
|
121
|
+
Array(results).map do |r|
|
|
122
|
+
{
|
|
123
|
+
title: r["title"].to_s,
|
|
124
|
+
url: r["url"].to_s,
|
|
125
|
+
content: r["content"].to_s,
|
|
126
|
+
score: (r["score"] || 0).to_f
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def normalize_infoboxes(infoboxes)
|
|
132
|
+
Array(infoboxes).map do |ib|
|
|
133
|
+
{
|
|
134
|
+
infobox: ib["infobox"].to_s,
|
|
135
|
+
id: ib["id"].to_s,
|
|
136
|
+
content: ib["content"].to_s,
|
|
137
|
+
urls: Array(ib["urls"]).map { |u| {title: u["title"].to_s, url: u["url"].to_s} }
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Searxng
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
|
|
4
|
+
class ConfigurationError < Error; end
|
|
5
|
+
|
|
6
|
+
class NetworkError < Error; end
|
|
7
|
+
|
|
8
|
+
class APIError < Error
|
|
9
|
+
attr_reader :status_code, :response_data, :uri
|
|
10
|
+
|
|
11
|
+
def initialize(message, status_code: nil, response_data: nil, uri: nil)
|
|
12
|
+
super(message)
|
|
13
|
+
@status_code = status_code
|
|
14
|
+
@response_data = response_data
|
|
15
|
+
@uri = uri
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require "fast_mcp"
|
|
2
|
+
require "searxng"
|
|
3
|
+
|
|
4
|
+
FastMcp = MCP unless defined?(FastMcp)
|
|
5
|
+
|
|
6
|
+
module Searxng
|
|
7
|
+
class Server
|
|
8
|
+
class NullLogger
|
|
9
|
+
attr_accessor :transport, :client_initialized
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@transport = nil
|
|
13
|
+
@client_initialized = false
|
|
14
|
+
@level = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_writer :level
|
|
18
|
+
attr_reader :level
|
|
19
|
+
|
|
20
|
+
def debug(*)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def info(*)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def warn(*)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def error(*)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fatal(*)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def unknown(*)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def client_initialized?
|
|
39
|
+
@client_initialized
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def set_client_initialized(value = true)
|
|
43
|
+
@client_initialized = value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def stdio_transport?
|
|
47
|
+
@transport == :stdio
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def rack_transport?
|
|
51
|
+
@transport == :rack
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.start
|
|
56
|
+
server = FastMcp::Server.new(
|
|
57
|
+
name: "searxng",
|
|
58
|
+
version: Searxng::VERSION,
|
|
59
|
+
logger: NullLogger.new
|
|
60
|
+
)
|
|
61
|
+
server.register_tool(SearxngWebSearchTool)
|
|
62
|
+
server.start
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class BaseTool < FastMcp::Tool
|
|
66
|
+
protected
|
|
67
|
+
|
|
68
|
+
def get_client
|
|
69
|
+
Client.new
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class SearxngWebSearchTool < BaseTool
|
|
74
|
+
tool_name "searxng_web_search"
|
|
75
|
+
description "Performs a web search using the SearXNG API. Use this to find information on the web. Aggregates results from multiple search engines."
|
|
76
|
+
|
|
77
|
+
arguments do
|
|
78
|
+
required(:query).filled(:string).description("The search query")
|
|
79
|
+
optional(:pageno).filled(:integer).description("Search page number (default: 1)")
|
|
80
|
+
optional(:max_results).filled(:integer).description("Max number of results to return in the response (default: 10). Use pageno for more.")
|
|
81
|
+
optional(:time_range).filled(:string).description("Time range: day, month, or year")
|
|
82
|
+
optional(:language).filled(:string).description("Language code (e.g. en, fr). Default: all")
|
|
83
|
+
optional(:safesearch).filled(:integer).description("Safe search: 0=none, 1=moderate, 2=strict")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def call(query:, pageno: 1, max_results: 10, time_range: nil, language: "all", safesearch: nil)
|
|
87
|
+
data = get_client.search(
|
|
88
|
+
query,
|
|
89
|
+
pageno: pageno,
|
|
90
|
+
time_range: time_range,
|
|
91
|
+
language: language,
|
|
92
|
+
safesearch: safesearch
|
|
93
|
+
)
|
|
94
|
+
format_search_result(data, max_results: max_results, pageno: pageno)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def format_search_result(data, max_results: 10, pageno: 1)
|
|
100
|
+
out = []
|
|
101
|
+
data[:infoboxes]&.each do |ib|
|
|
102
|
+
out << "Infobox: #{ib[:infobox]}"
|
|
103
|
+
out << "ID: #{ib[:id]}"
|
|
104
|
+
out << "Content: #{ib[:content]}"
|
|
105
|
+
out << ""
|
|
106
|
+
end
|
|
107
|
+
results = data[:results] || []
|
|
108
|
+
total = data[:number_of_results]
|
|
109
|
+
if results.empty?
|
|
110
|
+
out << "No results found"
|
|
111
|
+
else
|
|
112
|
+
limit = [max_results.to_i, 1].max
|
|
113
|
+
shown = results.first(limit)
|
|
114
|
+
shown.each do |r|
|
|
115
|
+
out << "Title: #{r[:title]}"
|
|
116
|
+
out << "URL: #{r[:url]}"
|
|
117
|
+
out << "Content: #{r[:content]}"
|
|
118
|
+
out << "Score: #{r[:score]}"
|
|
119
|
+
out << ""
|
|
120
|
+
end
|
|
121
|
+
if results.size > limit
|
|
122
|
+
out << "Showing #{limit} of #{results.size} on this page. Use pageno=#{pageno + 1} for more."
|
|
123
|
+
elsif total && total > results.size
|
|
124
|
+
out << "Showing #{shown.size} results (page #{pageno}). #{total} total. Use pageno=#{pageno + 1} for more."
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
out.join("\n").strip
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/searxng.rb
ADDED
data/sig/searxng.rbs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Searxng
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class ConfigurationError < Error; end
|
|
6
|
+
class NetworkError < Error; end
|
|
7
|
+
|
|
8
|
+
class APIError < Error
|
|
9
|
+
def initialize: (String message, ?status_code: Integer?, ?response_data: String?, ?uri: String?) -> void
|
|
10
|
+
attr_reader status_code: Integer?
|
|
11
|
+
attr_reader response_data: String?
|
|
12
|
+
attr_reader uri: String?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Client
|
|
16
|
+
def initialize: (
|
|
17
|
+
?base_url: String?,
|
|
18
|
+
?user: String?,
|
|
19
|
+
?password: String?,
|
|
20
|
+
?open_timeout: Integer,
|
|
21
|
+
?read_timeout: Integer,
|
|
22
|
+
?ca_file: String?,
|
|
23
|
+
?ca_path: String?,
|
|
24
|
+
?verify_mode: Integer?,
|
|
25
|
+
?user_agent: String?,
|
|
26
|
+
?configure_http: (Net::HTTP, URI::Generic) -> void
|
|
27
|
+
) -> void
|
|
28
|
+
def search: (
|
|
29
|
+
String query,
|
|
30
|
+
?pageno: Integer,
|
|
31
|
+
?time_range: String?,
|
|
32
|
+
?language: String,
|
|
33
|
+
?safesearch: Integer?
|
|
34
|
+
) -> Hash[Symbol, untyped]
|
|
35
|
+
end
|
|
36
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: searxng
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andrei Makarov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fast-mcp
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '0.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: rack
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '3.0'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '3.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: base64
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0.1'
|
|
53
|
+
type: :runtime
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0.1'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: rspec
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.13'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '3.13'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: webmock
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '3.26'
|
|
81
|
+
type: :development
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - "~>"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '3.26'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: vcr
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '6.0'
|
|
95
|
+
type: :development
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - "~>"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '6.0'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: rake
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '13.3'
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '13.3'
|
|
116
|
+
- !ruby/object:Gem::Dependency
|
|
117
|
+
name: simplecov
|
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - "~>"
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '0.22'
|
|
123
|
+
type: :development
|
|
124
|
+
prerelease: false
|
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - "~>"
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '0.22'
|
|
130
|
+
- !ruby/object:Gem::Dependency
|
|
131
|
+
name: rspec_junit_formatter
|
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - "~>"
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '0.6'
|
|
137
|
+
type: :development
|
|
138
|
+
prerelease: false
|
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - "~>"
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '0.6'
|
|
144
|
+
- !ruby/object:Gem::Dependency
|
|
145
|
+
name: simplecov-cobertura
|
|
146
|
+
requirement: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - "~>"
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '3.1'
|
|
151
|
+
type: :development
|
|
152
|
+
prerelease: false
|
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - "~>"
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '3.1'
|
|
158
|
+
- !ruby/object:Gem::Dependency
|
|
159
|
+
name: standard
|
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
|
161
|
+
requirements:
|
|
162
|
+
- - "~>"
|
|
163
|
+
- !ruby/object:Gem::Version
|
|
164
|
+
version: '1.52'
|
|
165
|
+
type: :development
|
|
166
|
+
prerelease: false
|
|
167
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
168
|
+
requirements:
|
|
169
|
+
- - "~>"
|
|
170
|
+
- !ruby/object:Gem::Version
|
|
171
|
+
version: '1.52'
|
|
172
|
+
- !ruby/object:Gem::Dependency
|
|
173
|
+
name: standard-custom
|
|
174
|
+
requirement: !ruby/object:Gem::Requirement
|
|
175
|
+
requirements:
|
|
176
|
+
- - "~>"
|
|
177
|
+
- !ruby/object:Gem::Version
|
|
178
|
+
version: '1.0'
|
|
179
|
+
type: :development
|
|
180
|
+
prerelease: false
|
|
181
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
182
|
+
requirements:
|
|
183
|
+
- - "~>"
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: '1.0'
|
|
186
|
+
- !ruby/object:Gem::Dependency
|
|
187
|
+
name: standard-performance
|
|
188
|
+
requirement: !ruby/object:Gem::Requirement
|
|
189
|
+
requirements:
|
|
190
|
+
- - "~>"
|
|
191
|
+
- !ruby/object:Gem::Version
|
|
192
|
+
version: '1.8'
|
|
193
|
+
type: :development
|
|
194
|
+
prerelease: false
|
|
195
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
196
|
+
requirements:
|
|
197
|
+
- - "~>"
|
|
198
|
+
- !ruby/object:Gem::Version
|
|
199
|
+
version: '1.8'
|
|
200
|
+
- !ruby/object:Gem::Dependency
|
|
201
|
+
name: standard-rspec
|
|
202
|
+
requirement: !ruby/object:Gem::Requirement
|
|
203
|
+
requirements:
|
|
204
|
+
- - "~>"
|
|
205
|
+
- !ruby/object:Gem::Version
|
|
206
|
+
version: '0.3'
|
|
207
|
+
type: :development
|
|
208
|
+
prerelease: false
|
|
209
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
210
|
+
requirements:
|
|
211
|
+
- - "~>"
|
|
212
|
+
- !ruby/object:Gem::Version
|
|
213
|
+
version: '0.3'
|
|
214
|
+
- !ruby/object:Gem::Dependency
|
|
215
|
+
name: rubocop-rspec
|
|
216
|
+
requirement: !ruby/object:Gem::Requirement
|
|
217
|
+
requirements:
|
|
218
|
+
- - "~>"
|
|
219
|
+
- !ruby/object:Gem::Version
|
|
220
|
+
version: '3.8'
|
|
221
|
+
type: :development
|
|
222
|
+
prerelease: false
|
|
223
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
224
|
+
requirements:
|
|
225
|
+
- - "~>"
|
|
226
|
+
- !ruby/object:Gem::Version
|
|
227
|
+
version: '3.8'
|
|
228
|
+
- !ruby/object:Gem::Dependency
|
|
229
|
+
name: rubocop-thread_safety
|
|
230
|
+
requirement: !ruby/object:Gem::Requirement
|
|
231
|
+
requirements:
|
|
232
|
+
- - "~>"
|
|
233
|
+
- !ruby/object:Gem::Version
|
|
234
|
+
version: '0.7'
|
|
235
|
+
type: :development
|
|
236
|
+
prerelease: false
|
|
237
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
238
|
+
requirements:
|
|
239
|
+
- - "~>"
|
|
240
|
+
- !ruby/object:Gem::Version
|
|
241
|
+
version: '0.7'
|
|
242
|
+
- !ruby/object:Gem::Dependency
|
|
243
|
+
name: appraisal
|
|
244
|
+
requirement: !ruby/object:Gem::Requirement
|
|
245
|
+
requirements:
|
|
246
|
+
- - "~>"
|
|
247
|
+
- !ruby/object:Gem::Version
|
|
248
|
+
version: '2.5'
|
|
249
|
+
type: :development
|
|
250
|
+
prerelease: false
|
|
251
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
252
|
+
requirements:
|
|
253
|
+
- - "~>"
|
|
254
|
+
- !ruby/object:Gem::Version
|
|
255
|
+
version: '2.5'
|
|
256
|
+
- !ruby/object:Gem::Dependency
|
|
257
|
+
name: memory_profiler
|
|
258
|
+
requirement: !ruby/object:Gem::Requirement
|
|
259
|
+
requirements:
|
|
260
|
+
- - "~>"
|
|
261
|
+
- !ruby/object:Gem::Version
|
|
262
|
+
version: '1.1'
|
|
263
|
+
type: :development
|
|
264
|
+
prerelease: false
|
|
265
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
266
|
+
requirements:
|
|
267
|
+
- - "~>"
|
|
268
|
+
- !ruby/object:Gem::Version
|
|
269
|
+
version: '1.1'
|
|
270
|
+
- !ruby/object:Gem::Dependency
|
|
271
|
+
name: rbs
|
|
272
|
+
requirement: !ruby/object:Gem::Requirement
|
|
273
|
+
requirements:
|
|
274
|
+
- - "~>"
|
|
275
|
+
- !ruby/object:Gem::Version
|
|
276
|
+
version: '3.9'
|
|
277
|
+
type: :development
|
|
278
|
+
prerelease: false
|
|
279
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
280
|
+
requirements:
|
|
281
|
+
- - "~>"
|
|
282
|
+
- !ruby/object:Gem::Version
|
|
283
|
+
version: '3.9'
|
|
284
|
+
description: Ruby gem providing a SearXNG HTTP client, CLI (search), and MCP server
|
|
285
|
+
for web search. Integrates with Cursor IDE via Model Context Protocol.
|
|
286
|
+
email:
|
|
287
|
+
- andrei@kiskolabs.com
|
|
288
|
+
executables:
|
|
289
|
+
- searxng
|
|
290
|
+
extensions: []
|
|
291
|
+
extra_rdoc_files: []
|
|
292
|
+
files:
|
|
293
|
+
- CHANGELOG.md
|
|
294
|
+
- LICENSE
|
|
295
|
+
- README.md
|
|
296
|
+
- bin/searxng
|
|
297
|
+
- lib/searxng.rb
|
|
298
|
+
- lib/searxng/client.rb
|
|
299
|
+
- lib/searxng/errors.rb
|
|
300
|
+
- lib/searxng/server.rb
|
|
301
|
+
- lib/searxng/version.rb
|
|
302
|
+
- sig/searxng.rbs
|
|
303
|
+
homepage: https://github.com/amkisko/searxng.rb
|
|
304
|
+
licenses:
|
|
305
|
+
- MIT
|
|
306
|
+
metadata:
|
|
307
|
+
source_code_uri: https://github.com/amkisko/searxng.rb
|
|
308
|
+
changelog_uri: https://github.com/amkisko/searxng.rb/blob/main/CHANGELOG.md
|
|
309
|
+
bug_tracker_uri: https://github.com/amkisko/searxng.rb/issues
|
|
310
|
+
documentation_uri: https://github.com/amkisko/searxng.rb#readme
|
|
311
|
+
rubygems_mfa_required: 'true'
|
|
312
|
+
rdoc_options: []
|
|
313
|
+
require_paths:
|
|
314
|
+
- lib
|
|
315
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
316
|
+
requirements:
|
|
317
|
+
- - ">="
|
|
318
|
+
- !ruby/object:Gem::Version
|
|
319
|
+
version: '3.1'
|
|
320
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
321
|
+
requirements:
|
|
322
|
+
- - ">="
|
|
323
|
+
- !ruby/object:Gem::Version
|
|
324
|
+
version: '0'
|
|
325
|
+
requirements: []
|
|
326
|
+
rubygems_version: 4.0.3
|
|
327
|
+
specification_version: 4
|
|
328
|
+
summary: SearXNG Ruby client and MCP server
|
|
329
|
+
test_files: []
|