- JavaScript 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Align the tool description with the CHAT-TLLX style: explicit 'Use PROACTIVELY' guidance to search before asserting anything not fully certain (news, versions, prices, people, products, docs/APIs, real-world claims), ground answers in results with source URLs, and keep the honest evidence-boundary caveat (snippets are evidence, not proof). |
||
| src | ||
| .gitignore | ||
| index.mjs | ||
| LICENSE | ||
| NOTICE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| test-e2e.mjs | ||
tllx-websearch
A Model Context Protocol (MCP) server that exposes the
CHAT-TLLX web_search tool — a text-only DuckDuckGo web search (titles, links, snippets,
no images) — as a standard MCP tool.
The search core (the DuckDuckGo HTML parser in src/web-search.mjs and the HTML→text stripper in
src/html-text.mjs) is copied from the CHAT-TLLX project
(TLLX/CHAT-TLLX, src/server/tools/web-search.js and
src/server/tools/html-text.js, AGPL-3.0-or-later). See NOTICE.
What is deliberately not included: the image tools (web_image_search, web_search_hybrid,
show_image, …) and the web_extract social reader. If you need those later, they are separate,
larger tools in CHAT-TLLX and would need their own port.
What it does
One tool: web_search
| Arg | Type | Required | Default | Notes |
|---|---|---|---|---|
query |
string | yes | — | Search query. Use a concise site:domain query for public social posts. |
limit |
int | no | 5 | 1–10. How many results to return. |
It returns both:
- a human/model-readable text block (same
[Result N]presentation CHAT-TLLX uses), and - a machine-readable
structuredContentobject:{ query, count, results: [{ number, title, url, snippet }] }, validated against a publishedoutputSchema.
Cancellations are honoured: an abort from the client aborts the in-flight HTTP request.
Safety invariants (inherited from CHAT-TLLX)
- 12 s hard per-request timeout.
- 2 MB hard response-size cap, enforced while streaming.
- Only one fixed, public endpoint is ever fetched (
https://duckduckgo.com/html/), so the original full SSRF guard (DNS pinning, private-range blocking) is not needed and was dropped; the timeout and size limits are kept (seesrc/safe-fetch.mjsfor the note).
Reliability note (inherited, not introduced here): this scrapes DuckDuckGo's HTML endpoint, the same way CHAT-TLLX does. It can be rate-limited, geo-restricted, or break if the page layout changes. When it can't parse results it degrades gracefully to
No search results found for: ….
Requirements
- Node.js ≥ 18 (uses the global
fetch). Developed and tested on Node 24. - npm (to install dependencies).
Install & run
cd tllx-websearch-mcp
npm install
npm start # = node index.mjs → speaks MCP JSON-RPC over stdio
The process reads the MCP protocol on stdin, writes it on stdout, and logs diagnostics to stderr (so stdout stays a clean protocol channel).
Quick self-test
node test-e2e.mjs
Spawns the server, then drives it through the official MCP client: initialize → tools/list →
tools/call. Exits 0 on success.
Wiring it into OpenCode
OpenCode spawns local MCP servers from mcp.servers. Add the server to your
opencode.jsonc (global ~/.config/opencode/opencode.jsonc, or a project
opencode.jsonc / .opencode/opencode.jsonc). Replace __ABS_PATH__ with the absolute path to
this folder:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"servers": {
// The key here becomes the tool namespace. Keep it short and unique:
// flat name -> tllx_web_search
// code mode -> tools.tllx.web_search(...)
"tllx": {
"type": "local",
"command": ["node", "__ABS_PATH__/index.mjs"]
}
}
}
}
Then verify it is connected:
opencode mcp list # should show: ✓ tllx connected
In the TUI you can also manage it with /mcps.
Calling the tool
With the tllx key above, the tool is namespaced under tllx. In OpenCode's default Code Mode
it appears as:
tools.tllx.web_search({ query: "Model Context Protocol", limit: 3 })
(If your client does not use Code Mode, the flat name is tllx_web_search.)
The
mcp.serverskey is yours to choose. It only controls the namespace OpenCode derives for the tool name — it does not change the server's behaviour. Avoid a key that collides with an existing server or with OpenCode's built-inwebsearchtool.
Project layout
tllx-websearch-mcp/
├── index.mjs # MCP server entry (stdio transport, registers web_search)
├── src/
│ ├── web-search.mjs # copied DuckDuckGo parser + webSearch() + result formatter
│ ├── html-text.mjs # copied htmlToText()/truncateText()
│ └── safe-fetch.mjs # timeout + size-capped fetch (adapted from CHAT-TLLX)
├── test-e2e.mjs # end-to-end stdio client test
├── package.json
└── NOTICE # attribution for the copied CHAT-TLLX code
Design notes / future work
web_extract(CHAT-TLLX's social-aware page reader) is a much larger tool and is not ported. Adding it later would mean portingsrc/server/tools/web-extract.jsand its adapters.- Image search is intentionally out of scope per the current requirement.
- The
webSearchwrapper returns structured data (no CHAT-TLLX session state). If you later want cross-call result numbering (e.g. to feed anextract-style tool), you would add a server-side session map keyed by thesessionIdthe SDK passes to the tool callback (extra.sessionId) — CHAT-TLLX does exactly this inweb-tools.js.
License
This project is licensed under AGPL-3.0-or-later (see LICENSE). Portions are copied from CHAT-TLLX (AGPL-3.0-or-later) — see NOTICE for attribution.