Quick answer
How do I add a web search MCP server to Claude Code or Claude Desktop?
Save the short Python server on this page, which exposes search_web and read_urls tools calling LLMWebSearch, then register it with claude mcp add or in claude_desktop_config.json, with your key in env. The model can then search the live web and read the top pages as Markdown. A search costs 1 credit, from $1.20/1K searches as of October 2026. No KYC; pay in crypto.
The MCP server at a glance
| Language | Python 3.10+, with the mcp and httpx packages |
|---|---|
| Transport | stdio: the client starts the server as a local process |
| Tool 1 | search_web(query, max_results=5, read=3) → numbered sources with URLs and page text |
| Tool 2 | read_urls(urls) → the main content of 1 to 20 known pages |
| Names | Not web_search or web_fetch: Claude Code and OpenClaw already ship tools with those names |
| Key | Read from LLMWEBSEARCH_API_KEY in the server's environment |
| Billing | One credit pays for one search: up to 20 ranked results, with the pages of the first 3 read and returned as Markdown. Reading 4 to 10 pages makes it 2 credits. A call that ends in an error costs nothing. Reading pages whose URLs you already have costs 1 credit for each started group of 3 pages returned with content, up to 20 URLs per call; refused or failed URLs are not counted |
Why are the tools called search_web and read_urls?
Because two tools with the same name confuse the client and the model. Claude Code and OpenClaw each include built-in tools named web_search and web_fetch. Distinct names, with clear descriptions, let the model choose ours on purpose and let you tell the calls apart in logs.
Both tools return text, not raw JSON. Each source starts with a number, a title and its URL, so the model can cite [1] or [2] and the reader can check the page.
import os
import httpx
from mcp.server.fastmcp import FastMCP
BASE = "https://llmwebsearch.com/api/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
mcp = FastMCP("llmwebsearch")
def _call(path: str, body: dict) -> dict:
resp = httpx.post(BASE + path, json=body, headers=HEADERS, timeout=90)
data = resp.json()
if resp.status_code != 200:
err = data.get("error", {})
raise RuntimeError(err.get("code", str(resp.status_code)) + ": " + err.get("message", ""))
return data
def _page(n: int, item: dict) -> str:
head = f"[{n}] {item.get('title') or item['url']}\n{item['url']}\n"
if item.get("content_status") == "ok":
return head + item["content"]
return head + "(not read: " + item.get("content_status", "unknown") + ") " + item.get("snippet", "")
@mcp.tool()
def search_web(query: str, max_results: int = 5, read: int = 3) -> str:
"""Search the live web and return numbered sources; the first `read` pages include their text.
Use it for current facts, documentation and news. Cite sources by their number.
"""
body = {"q": query, "max_results": max(1, min(max_results, 20)), "read": max(0, min(read, 10)), "max_chars": 5000}
data = _call("/search", body)
return "\n\n".join(_page(i, r) for i, r in enumerate(data["results"], start=1))
@mcp.tool()
def read_urls(urls: list[str]) -> str:
"""Read 1 to 20 public web pages whose URLs you already have and return their main text."""
data = _call("/extract", {"urls": urls[:20], "max_chars": 12000})
return "\n\n".join(_page(i, r) for i, r in enumerate(data["results"], start=1))
if __name__ == "__main__":
mcp.run()
How do I install and test the server?
Create a virtual environment, install mcp and httpx, and save the file above. mcp is the official Python SDK of the Model Context Protocol; its FastMCP class turns typed Python functions into tools and uses each docstring as the tool's description.
Start the file once from a terminal with your key set. It then waits quietly for a client on standard input, which means it works; stop it with Ctrl+C. Errors from the API are raised as tool errors with their code, so the model sees rate_limited or insufficient_credits in plain words.
python -m venv .venv
. .venv/bin/activate
pip install "mcp[cli]" httpx
LLMWEBSEARCH_API_KEY="your-key" python llmwebsearch_mcp.pyHow do I add it to Claude Code?
Use claude mcp add. Claude Code's documentation gives the form claude mcp add [options] <name> -- <command> [args...]: its own options come first, then a double dash, then the command that starts the server. Environment variables go in --env KEY=value, and at least one other option, such as --transport stdio, must sit between --env and the server name.
Add --scope user to make the server available in all your projects; the default scope, local, covers the current project only. Use absolute paths for the Python of your virtual environment and for the server file.
claude mcp add --env LLMWEBSEARCH_API_KEY=your-key --transport stdio --scope user llmwebsearch \
-- /home/you/llmwebsearch/.venv/bin/python /home/you/llmwebsearch/llmwebsearch_mcp.pyHow do I add it to Claude Desktop and other clients?
Claude Desktop reads servers from claude_desktop_config.json, under mcpServers. Give the server a name, the command, its arguments and an env object holding the key, then restart the app; search_web and read_urls appear in the tools list.
Other MCP clients, such as Cursor, ask for the same three things in their own MCP settings: a command, its arguments and environment variables. The key never appears in a tool argument or in the conversation.
{
"mcpServers": {
"llmwebsearch": {
"command": "/home/you/llmwebsearch/.venv/bin/python",
"args": ["/home/you/llmwebsearch/llmwebsearch_mcp.py"],
"env": { "LLMWEBSEARCH_API_KEY": "your-key" }
}
}
}How do I keep searches cheap and short?
Tell the model how to search in your instructions: one or two searches per question, then read more only if the sources disagree. The server already caps each page at 5,000 characters in search_web and 12,000 in read_urls; lower those numbers for small models.
Each search_web call with read up to 3 is one credit. An assistant that runs 40 searches a day uses about 1,200 credits a month, so 5,000 searches ($9.25) lasts around four months. Search credits have no expiry date: what you do not use this month is still there next year.
What does the model see when a page cannot be read?
The source still appears, with its number, title and URL, followed by the reason in brackets and the search snippet. Our reader respects robots.txt: it introduces itself as LLMWebSearch, follows the rules a site writes for it or for all robots, and leaves out the content of any page it is not allowed to read. When a site turns our reader away, the result comes back without content and with the site's status: we never solve CAPTCHAs, sign in to accounts or work around a block or a paywall. The model can then rely on the other sources or say that one page was not available.
MCP server: common questions
Is there an official LLMWebSearch MCP package or hosted endpoint?
No. The file on this page is the integration: about 50 lines you can read and change. It runs on your machine, and your key only travels to our API.
Should I turn off Claude Code's built-in web search?
No need. Both can stay; the descriptions tell the model what each does. Keep ours when you want page text in the results, a fixed price per search, or one place for your search logs.
Can I use it with OpenClaw?
Yes, as an MCP server registered with openclaw mcp add. The OpenClaw page gives the command and explains how it sits next to OpenClaw's built-in search providers.
Does the server keep my searches?
No. It holds nothing between calls. On our side, We do not train any model on your queries or on what the API returns, and we add no rule of our own against storing results; the rights of the sites whose pages you read still apply.
Sources (1)
- Connect Claude Code to tools via MCP — Claude Code docs (read 2026-10-22)