Skip to content

Add searxng-search plugin: self-hosted SearXNG web search Skill - #1

Open
Fectivnfy112357 wants to merge 1 commit into
MiniMax-AI:mainfrom
Fectivnfy112357:feat/searxng-search
Open

Add searxng-search plugin: self-hosted SearXNG web search Skill#1
Fectivnfy112357 wants to merge 1 commit into
MiniMax-AI:mainfrom
Fectivnfy112357:feat/searxng-search

Conversation

@Fectivnfy112357

@Fectivnfy112357 Fectivnfy112357 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

What this PR adds

A new Skill plugin, searxng-search, that lets the agent run a web
search query against the user's own self-hosted SearXNG instance via a
zero-dependency Python script. Operators that already self-host SearXNG
(a privacy-respecting metasearch engine) get a first-class search tool
without going through a vendor API or installing another binary.

Filters supported out of the box: categories, engines, time range,
language, safe search, pagination. Bearer and basic auth, with secrets
referenced as $ENV_VAR so plaintext never lives in the config file.

The problem it solves

Web search inside an agent usually means a vendor API with credentials
and quotas. If you already run SearXNG, this skill gives the agent a
direct search tool against it — one config file, no vendor SDK, no
tracking.

Try it

After installing from /pluginsLocal and configuring your
instance (see skills/searxng-search/references/configuration.md),
ask the agent:

search the web for the latest SearXNG documentation

Expected result: a formatted list of results (title, URL, snippet)
from your SearXNG instance, filtered by the requested category / time
range / language.

Direct usage from the skill directory:

python3 scripts/search.py -c news -t day "latest tech news"
python3 scripts/search.py -e google,duckduckgo -p 2 "rust programming"
python3 scripts/search.py -l zh-CN -n 10 "开源搜索引擎"

Dependencies and supported platforms

  • Python 3.11+ at runtime (uses tomllib; legacy JSON config still
    works on older Python). Zero pip dependencies.
  • A self-hosted SearXNG instance reachable over HTTP(S) — you
    provide it; no public instance is bundled.
  • Tested on Windows (Python 3.13) and POSIX. The node --test bridge
    in test/searxng-search.test.mjs uses the platform-appropriate
    interpreter discovery order (py -3pythonpython3 on
    Windows; python3python on POSIX) and validates each candidate
    with --version before running, so it does not produce a false
    pass on a host that has no usable Python.

Network and data behavior

This skill has two levels of network destinations; both matter.

  • Direct (this script): the script makes a single request to the
    configured base_url. HTTPS by default; plain HTTP is allowed only
    for loopback hosts (127.0.0.1, ::1, localhost) or for non-loopback
    hosts with an explicit allow_insecure_http = true opt-in, which
    emits a strong warning because Authorization headers and search
    queries would travel in cleartext. The script never follows HTTP
    redirects: a 30x response is an error, because following a redirect
    could forward the Authorization header to a host the user did not
    configure. Point base_url directly at the final endpoint, or
    front the instance with a same-origin reverse proxy.
  • Downstream (your SearXNG instance): the instance then forwards
    the query, language code, and selected categories to
    the upstream engines it has been configured with (Google, Bing,
    DuckDuckGo, Brave, Baidu, etc., as enabled by the instance
    operator). Those engines receive the request content from the
    instance — the script does not see or control that hop. If query
    contents reaching the upstream engines is a concern, configure
    your instance to use engines you trust, or self-host engines
    locally.

No telemetry. No third-party services run by this script.
Authorization headers and search queries are sent only to the
configured base_url; the script never sends them anywhere else.

Compliance declarations

  • No credentials, private endpoints, or personal data in the repo
    or in the test fixtures. The test suite is fully offline (no
    network, no gh binary, no real SearXNG instance).
  • No installer. The script does not write to ~/.dsh,
    ~/.minimax, ~/.pi, or any other user configuration directory.
    It only reads the local config file at
    ~/.config/agents/searxng.toml (or .json).
  • No symlinks in the plugin tree.
  • No native binaries. The script uses Python's standard library
    only (urllib, tomllib, json, argparse, base64).
  • No hidden side effects. No file generation outside the user's
    own config; no MCP server; no commands besides the single Skill
    that calls python3 scripts/search.py.
  • All error paths route through a redact_secrets pipeline
    (exact-value registry + shape-based fallback). 401 / 403 / 407
    response bodies are not echoed because they frequently reflect
    back the credentials the server saw. Custom headers values use
    the same $ENV_VAR resolution and redaction registration as
    auth.*.
  • Response body is capped at 10 MiB to prevent runaway reads on
    a misbehaving or hijacked endpoint.

Test evidence

  • py -3 skills/searxng-search/scripts/run_tests.py77 pass, 2
    skipped (POSIX-only, expected on Windows), 0 fail
    . The suite
    covers:
    • URL validation (HTTPS pass; loopback HTTP pass; non-loopback HTTP
      reject; opt-in warning; non-http(s) schemes reject)
    • HTTP redirect handling (all 3xx codes blocked; diagnostic strips
      query and fragment from Location:; opener installs the safe
      handler exactly once and no default redirect handler; integration
      through main() does not echo the Authorization header)
    • Auth header construction (bearer / basic; env-var resolution;
      token never leaks into the URL; custom headers values follow
      the same env-var + redaction rules as auth.*; User-Agent
      override; non-string header values rejected)
    • Config loading (missing file, invalid TOML / JSON, missing
      base_url, env-var resolution, numeric type validation,
      POSIX-mode warning with Windows skip)
    • Credential redaction (5 shape patterns, exact-value registry,
      short / non-shape tokens, substring-first ordering, reset,
      empty-value no-op, idempotency, stderr redaction)
    • Invalid response handling (non-JSON, structured error,
      HTTPError body redacted, URL error, timeout, 401 / 403 / 407
      body suppressed
      , JSON-array root, non-UTF-8, oversized
      response, oversized error body)
    • Timeout configuration (default 30s, custom value pass-through)
  • node --test test/searxng-search.test.mjs1 pass, 0 fail.
    The Node bridge spawns the first usable Python interpreter with
    --version validation and asserts python run_tests.py exits 0,
    so the repo's node --test discoverer picks this up under
    npm test.
  • node scripts/validate.mjsOK plugin Fectivnfy112357/searxng-search. The package shape (manifest
    fields, SKILL.md frontmatter, LICENSE, README, no symlinks, no
    BOM, no TODO residue) is clean.

Files (18, +2379 / -0)

plugin.json
README.md
LICENSE
skills/searxng-search/SKILL.md
skills/searxng-search/.gitignore
skills/searxng-search/references/configuration.md
skills/searxng-search/scripts/search.py
skills/searxng-search/scripts/run_tests.py
skills/searxng-search/scripts/tests/__init__.py
skills/searxng-search/scripts/tests/_fixtures.py
skills/searxng-search/scripts/tests/test_auth_header.py
skills/searxng-search/scripts/tests/test_config.py
skills/searxng-search/scripts/tests/test_invalid_response.py
skills/searxng-search/scripts/tests/test_redaction.py
skills/searxng-search/scripts/tests/test_redirect.py
skills/searxng-search/scripts/tests/test_timeout.py
skills/searxng-search/scripts/tests/test_url_validation.py
test/searxng-search.test.mjs

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

A zero-dependency Python Skill that lets the agent run search queries
against the user's own self-hosted SearXNG instance. Supports bearer /
basic auth, TOML or JSON config, and the standard SearXNG filters
(categories, engines, time range, language, safe search, pagination).

Solves: web search inside an agent usually means a vendor API with
credentials and quotas. Operators that already run SearXNG (a
privacy-respecting metasearch engine) get a first-class search tool
against their own instance — one config file, no vendor SDK, no
tracking.

Security model (matched to mcode-plugin-guide red lines):

- HTTPS by default. Plain HTTP is accepted only for loopback hosts
  (127.0.0.1, ::1, localhost) or for non-loopback hosts when the
  config contains an explicit `allow_insecure_http = true` opt-in,
  which emits a strong warning.
- Auth values can be referenced via `$ENV_VAR` / `${ENV_VAR}` in the
  config so the plaintext never lives in the file. On POSIX the
  script warns when the file is readable beyond the owner.
- HTTP redirects are blocked entirely (custom `HTTPRedirectHandler`).
  The default urllib handler would forward the `Authorization` header
  to the redirect target, which can leak the token to a different
  origin and silently downgrade HTTPS to HTTP. Point `base_url`
  directly at the final endpoint, or front the instance with a
  same-origin reverse proxy.
- 401 / 403 / 407 response bodies are not echoed to stderr because
  they frequently reflect back the credentials the server saw.
  Other 4xx / 5xx bodies run through a two-pass redactor: an
  exact-value secret registry (per request) plus shape-based
  fallbacks (Bearer / Basic / github_pat_ / gh[pousr]_ / token= /
  common env-var names).
- Numeric config fields are type-validated at load time
  (`timeout = "30"` etc. fail fast with a clear message instead of
  surfacing a raw TypeError from urllib).
- Response body is capped at 10 MiB to prevent runaway reads on a
  misbehaving or hijacked endpoint.

Network (two levels, both disclosed in README, SKILL.md, and
plugin.json):

- Direct: the script makes a single request to the user's
  configured `base_url`. Nothing else.
- Downstream: the user's SearXNG instance then forwards the query,
  language code, and selected categories to its own configured
  upstream engines (Google, Bing, DuckDuckGo, Brave, Baidu, etc., as
  enabled by the instance operator). The script does not see or
  control that hop.

Tested:

- `py -3 skills/searxng-search/scripts/run_tests.py` → 77 pass,
  2 POSIX-only skipped (expected on Windows).
- `node --test test/searxng-search.test.mjs` → 1 pass, 0 fail
  (the Node bridge that the repo's `npm test` will discover).
- `node scripts/validate.mjs` → `OK plugin
  Fectivnfy112357/searxng-search`.

Files (28):

- `plugin.json`, `README.md`, `LICENSE` (MIT)
- `skills/searxng-search/SKILL.md` and `.gitignore`
- `skills/searxng-search/references/configuration.md`
- `skills/searxng-search/scripts/search.py` (the runtime)
- `skills/searxng-search/scripts/run_tests.py` (unittest entry)
- `skills/searxng-search/scripts/tests/` (7 test modules + fixtures)
- `test/searxng-search.test.mjs` (Node bridge for the repo's
  `node --test` discoverer)

No installer. No write operations outside reading the local config
file. No telemetry. No third-party services run by this script.
No symlinks. No native binaries. No credentials or private endpoints
are committed; the test suite is fully offline.
antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 22, 2026
Fixes for review comments from hetaoBackend (commit fce7c5f):

  MiniMax-AI#1 detector hard-coded path: resolve the [userprofile]/.minimax-code
     directory at runtime via the mcode node process cmdline (regex on
     @minimax-ai/code/cli.js), with fallbacks to $env:USERPROFILE/.minimax-code,
     $env:APPDATA/minimax-code, and the current working directory.
     Override with -Root [path].

  MiniMax-AI#2 idle fallback unreachable: mtime cache now returns the last inferred
     message instead of null, so the 60s stale -> idle branch fires every
     poll. Verified locally: idle :: already idle 195s after 65s of inactivity.

  #2b session log: prefer ledger.jsonl (mcode v2 event stream) and fall
     back to messages.jsonl when ledger is missing. Both formats are handled
     in Infer-State (kind/phase for ledger, message.role for messages).

  MiniMax-AI#3 PID reuse safety: start/stop-{island,detect-island}.ps1 now verify
     the target PID command line contains the expected script path before
     acting. Stale PIDs and PID-reused processes are refused with a
     REFUSED log line instead of being killed.

  MiniMax-AI#4 wrap-tool.ps1 shell-injection: removed Invoke-Expression entirely.
     The wrapper is now status-only; the agent runs the command via mcode's
     own bash tool and passes -ExitCode to publish the outcome.
     Documented in README + SKILL.md.

  MiniMax-AI#5 README: -Enable -> -Action Enable to match autostart.ps1 parameter set.

  MiniMax-AI#6 start-island.ps1 readiness: dropped the 'about to ShowDialog' log wait
     (which was never emitted). Now polls MainWindowHandle != 0 every 500ms
     for up to 8s.

Tests: validator reports OK plugin antianqi/mcode-island. wrap-tool
6-state matrix verified locally (working / done / waiting / error).
antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 22, 2026
The previous implementation had a "fast path" that did
`await import(lintScript).then(mod => mod.lint(skillPath))` in-process.
The default host linter at
`~/.minimax/.builtin-skills/skill-creator/scripts/lint-skill.js` calls
`process.exit(2)` when invoked without CLI arguments, and `process.exit`
is not catchable from JS — so a default invocation (no `run_lint=false`
override) terminated the entire MCP server before it could return a
JSON-RPC response.

  - `lib/lint.js`: drop the in-process fast path; always run the
    linter as a child process. Cost: one extra `node` spawn + a
    staged `.mjs` in `os.tmpdir()` per `convert` call (~100 ms). The
    trade is worth it: the MCP server is now guaranteed to survive a
    misbehaving linter.
  - `lib/lint.js`: pre-flight `fs.stat(lintScript)` so a missing host
    linter surfaces as `{ ok: false, code: -1, stderr: 'lint script
    not available: ...' }` instead of an uncaught ENOENT from
    `fs.readFile` inside `stageMjsInTmp`.
  - `tests/lint.test.mjs`: rewrite around the subprocess-only model.
    Replace the fast-path test with three cases:
    - subprocess path stages in `os.tmpdir()`, install dir untouched
    - linter calls `process.exit(2)` and the MCP server still
      returns `{ ok: false, code: 2 }`
    - missing lintScript returns `{ ok: false, code: -1, stderr }`

`node --test plugins/antianqi/skill-bridge/tests/*.test.mjs` reports
54/54 pass (was 53/53; +1 new case for missing linter).
antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 23, 2026
MiniMax-AI#2)

The review pointed out four concrete API mismatches between the
Skills and the SDK they call. We pulled the actual
`acp_tools.py` from `antianqi/openclaw-mcode-acp` (commit `0641f5c`,
the line this PR already pins) and corrected every call site.

  - **acp-task-dispatch/SKILL.md** (review MiniMax-AI#1):
    - `from acp_tools import create_task, get_task, list_history` →
      `history` (the function is named `history`, not `list_history`).
    - `task = create_task(...)` then `task["task_id"]` →
      `task_id = create_task(...)` (the function returns the
      `task_id` string directly, not a mapping).
    - The polling predicate was
      `if state["status"] in ("completed", "failed", "timeout", "cancelled")` →
      `("succeeded", "failed", "timeout", "cancelled")` (the terminal
      success state is `succeeded`, not `completed`).
    - `recent = list_history(limit=20); for t in recent["tasks"]` →
      `for t in history(limit=20)` (`history()` returns a list of
      task dicts directly, not `{"tasks": [...]}`).

  - **acp-collab/SKILL.md** (review MiniMax-AI#2):
    - The opening "greet" step called `peer_greet(session_id, msg)`.
      `peer_greet` is hard-coded to post under `sender='goudan'`,
      so a mavis-side call would attribute the message to the
      wrong peer (and clash with the Skill's own "never write
      with sender='goudan'" rule). Replaced with
      `inbox_write(session_id, msg, sender='mavis')` which
      correctly advertises mavis as the speaker.
    - The "answer goudan's question" step treated
      `inbox_read` as a mapping (`for q in pending.get("messages", [])`).
      `inbox_read` returns a **list** directly, not `{"messages": ...}`.
      Simplified the loop accordingly.

  - **README.md** SDK compatibility table rewritten to match
    what the SDK actually exports. Every row now shows the
    correct return type. Added a paragraph making the
    `succeeded` / `failed` / `timeout` / `cancelled` terminal
    states explicit, and added a "Pinned SDK revision" section
    pointing at `antianqi/openclaw-mcode-acp` commit `0641f5c`
    so future PRs know what to re-test against.

`node scripts/validate.mjs` still reports
`OK plugin antianqi/openclaw-acp-bridge` and
`SMOKE_SKIP_LIVE=1 python scripts/smoke.py` reports 8/8 PASS.
antianqi added a commit to antianqi/MiniMax-Code-Plugins-1 that referenced this pull request Aug 23, 2026
MiniMax-AI#2)

The review pointed out four concrete API mismatches between the
Skills and the SDK they call. We pulled the actual
`acp_tools.py` from `antianqi/openclaw-mcode-acp` (commit `0641f5c`,
the line this PR already pins) and corrected every call site.

  - **acp-task-dispatch/SKILL.md** (review MiniMax-AI#1):
    - `from acp_tools import create_task, get_task, list_history` →
      `history` (the function is named `history`, not `list_history`).
    - `task = create_task(...)` then `task["task_id"]` →
      `task_id = create_task(...)` (the function returns the
      `task_id` string directly, not a mapping).
    - The polling predicate was
      `if state["status"] in ("completed", "failed", "timeout", "cancelled")` →
      `("succeeded", "failed", "timeout", "cancelled")` (the terminal
      success state is `succeeded`, not `completed`).
    - `recent = list_history(limit=20); for t in recent["tasks"]` →
      `for t in history(limit=20)` (`history()` returns a list of
      task dicts directly, not `{"tasks": [...]}`).

  - **acp-collab/SKILL.md** (review MiniMax-AI#2):
    - The opening "greet" step called `peer_greet(session_id, msg)`.
      `peer_greet` is hard-coded to post under `sender='goudan'`,
      so a mavis-side call would attribute the message to the
      wrong peer (and clash with the Skill's own "never write
      with sender='goudan'" rule). Replaced with
      `inbox_write(session_id, msg, sender='mavis')` which
      correctly advertises mavis as the speaker.
    - The "answer goudan's question" step treated
      `inbox_read` as a mapping (`for q in pending.get("messages", [])`).
      `inbox_read` returns a **list** directly, not `{"messages": ...}`.
      Simplified the loop accordingly.

  - **README.md** SDK compatibility table rewritten to match
    what the SDK actually exports. Every row now shows the
    correct return type. Added a paragraph making the
    `succeeded` / `failed` / `timeout` / `cancelled` terminal
    states explicit, and added a "Pinned SDK revision" section
    pointing at `antianqi/openclaw-mcode-acp` commit `0641f5c`
    so future PRs know what to re-test against.

`node scripts/validate.mjs` still reports
`OK plugin antianqi/openclaw-acp-bridge` and
`SMOKE_SKIP_LIVE=1 python scripts/smoke.py` reports 8/8 PASS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant