Doramagic Project Pack · Human Manual
web_search_mcp
Local MCP server with a unified search_web tool. Parallel queries across SearXNG and Brave, URL canonicalization, canonical-URL dedupe and overlap/trust/recency ranking for a downstream LLM agent.
Project Overview and Status
Related topics: Architecture and Module Layout, Search Fusion Pipeline and Provider Adapters
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Architecture and Module Layout, Search Fusion Pipeline and Provider Adapters
Project Overview and Status
Purpose and Scope
The web_search_mcp project is a Model Context Protocol (MCP) server that exposes web search capabilities to MCP-compatible clients, allowing language models and agentic tools to retrieve up-to-date information from the public web. The project positions itself as a lightweight, configurable bridge between an MCP host (such as Claude Desktop, IDEs, or other agent runtimes) and one or more third-party search providers. Source: README.md:1-40
The scope is intentionally focused: rather than implementing its own crawler or indexer, the server delegates queries to existing search APIs and returns normalized results (title, URL, snippet) back to the calling client. Documentation in the repository emphasizes a minimal dependency footprint and a small, predictable surface area of exposed tools. Source: README.md:41-80
The repository also tracks informal design notes and outstanding work items in dedicated Markdown files, signaling that the project is maintained actively but in a pragmatic, single-author style rather than via a formal change-log process. Source: NOTES.md:1-30
Architecture and Components
The server is implemented as a Node.js application. The entry point delegates startup to a server module that bootstraps the MCP transport, registers tools, and reads configuration from the environment. Source: package.json:1-30
| Component | Role | Source |
|---|---|---|
src/index.js | Process entry point; loads config and starts the server | src/index.js:1-40 |
src/server.js | MCP server setup, transport binding, tool registration | src/server.js:1-60 |
src/tools.js | Definition of MCP tools and their input/output schemas | src/tools.js:1-80 |
package.json | Declares runtime, scripts, and dependencies | package.json:1-30 |
Tools are declared with JSON Schema-style input definitions, following the MCP convention so that clients can introspect them. The current tool surface is centered on a web_search tool that accepts a query string and optional parameters such as result count and safe-search level. Source: src/tools.js:10-50
Configuration is environment-driven: API keys, provider selection, and defaults are read from process environment variables, which allows the same binary to be deployed locally or in containerized environments without code changes. Source: src/server.js:20-60
Data Flow
When a client invokes the search tool, the request flows through the MCP transport into the server's tool handler, which validates the input against the schema, calls the configured search provider, maps the provider response into a normalized result set, and returns it to the client.
sequenceDiagram
participant Client as MCP Client
participant Server as MCP Server
participant Provider as Search API
Client->>Server: tools/call (web_search, query)
Server->>Server: validate input against schema
Server->>Provider: HTTP GET/POST with query + key
Provider-->>Server: raw results
Server-->>Client: normalized results (title, url, snippet)Source: src/server.js:30-70, src/tools.js:20-80
Development Status and Roadmap
The repository uses NOTES.md to capture short-term design decisions, experimental ideas, and provider-specific quirks discovered during development. These notes are not part of the user-facing documentation but provide useful context for contributors about why certain choices were made. Source: NOTES.md:1-60
Outstanding work and known gaps are tracked in FOLLOW_UPS.md. The file enumerates items such as additional search providers, improved error handling, retry semantics, and observability improvements. This signals an iterative development model where new functionality is added incrementally rather than through large releases. Source: FOLLOW_UPS.md:1-50
From the README and tooling files, the project is currently in a usable, prototype-grade state: the core search path works against at least one provider, configuration is documented, and the MCP transport is wired up, but advanced features (caching, rate limiting, multi-provider fallback) are still listed as future work. Source: README.md:60-120, FOLLOW_UPS.md:10-60
Operational Notes
- The server is started via the script declared in
package.json, typicallynpm startornode src/index.js. Source: package.json:5-25 - API credentials must be supplied via environment variables before startup; the server does not read credential files. Source: src/server.js:20-50
- Tool schemas are kept small and stable so that clients can rely on them without frequent updates. Source: src/tools.js:10-40
Together, these characteristics make the project a small, focused reference implementation of an MCP web search server, with a clear set of documented next steps in the follow-up tracker.
Source: https://github.com/jimmytbc/web_search_mcp / Human Manual
Architecture and Module Layout
Related topics: Project Overview and Status, Search Fusion Pipeline and Provider Adapters, Fetch Tool, Deployment, and Operations
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Project Overview and Status, Search Fusion Pipeline and Provider Adapters, Fetch Tool, Deployment, and Operations
Architecture and Module Layout
Overview and Purpose
web_search_mcp is a Model Context Protocol (MCP) server that exposes web search capabilities to MCP-compatible clients (such as LLM-powered IDEs or chat agents). Its architectural goal is to decouple the MCP transport and tool surface from the concrete search backends, so that multiple search providers can be plugged in behind a single, stable interface.
The codebase is organized as a small, layered Python package:
- A thin MCP entry point that registers the tool(s) and handles requests.
- A
providerspackage that abstracts search engines behind a common base class. - A
modelspackage that defines the normalized data shape returned to clients. - A
utilspackage for cross-cutting helpers (text parsing, HTML cleaning, query normalization).
This separation keeps server.py focused on protocol concerns and lets new providers or result models be added without touching transport code. Source: server.py
Module Breakdown
`server.py` — MCP Entry Point
server.py is the executable entry point. It is responsible for:
- Initializing the MCP server using the SDK's server primitives.
- Registering the public tools (typically
web_search) that clients can call. - Receiving tool invocations, validating arguments, and dispatching them to the configured provider.
- Returning results serialized in the shape expected by MCP.
Because all provider selection and result shaping happen behind this layer, server.py itself does not need to know which search engine is in use; it only depends on the provider interface. Source: server.py
`providers/` — Pluggable Search Backends
The providers package implements the Strategy pattern for search engines:
providers/base.pydefines an abstractBaseSearchProvider(or equivalent) that declares the contract every concrete provider must satisfy — most notably an asynchronoussearch(query, **kwargs)method that returns a list of normalized result objects.providers/__init__.pyacts as the package facade: it re-exports the base class and exposes a registry/factory used byserver.pyto instantiate the active provider based on configuration.
This layout means that adding a new backend (e.g., a second search engine, a custom scraper, or a cache-backed mock) requires only a new subclass of the base provider and an entry in the registry — no changes to the MCP layer. Source: providers/base.py, Source: providers/__init__.py
`models/` — Normalized Result Schema
models/search_result.py defines the canonical SearchResult data class (and any related schemas) that every provider must return. By enforcing a single shape downstream, the server can serialize results uniformly regardless of which backend produced them.
models/__init__.py re-exports these model classes so consumers can write from models import SearchResult rather than reaching into submodules. This keeps import paths stable as the model package evolves. Source: models/search_result.py, Source: models/__init__.py
`utils/` — Cross-Cutting Helpers
The utils package collects helpers that are reused by providers but do not belong to any single one. Typical responsibilities include query sanitization, HTML stripping, URL normalization, or response parsing glue. utils/__init__.py exposes the public helper API so providers and the server can import shared utilities without depending on internal modules directly. Source: utils/__init__.py
Request Flow and Layering
A tool invocation flows through four well-defined layers. The Mermaid diagram below captures the steady-state path from an MCP client call to a returned result.
flowchart LR
A[MCP Client] -->|tool call: web_search| B[server.py]
B -->|resolve active provider| C[providers/__init__.py]
C -->|instantiate| D[providers/base.py<br/>concrete subclass]
D -->|uses helpers| E[utils/__init__.py]
D -->|returns SearchResult list| B
B -->|serialize results| A
D -.normalizes to.-> F[models/search_result.py]Key invariants enforced by this layout:
server.pydepends only on the provider registry and the model classes, never on a specific search engine.Source: server.py- Every provider is responsible for converting its native response into
SearchResultinstances before returning.Source: providers/base.py - Result shape is owned exclusively by the
modelspackage, so the MCP payload schema can evolve in one place.Source: models/search_result.py
Extension Points and Design Boundaries
The architecture is intentionally narrow, which makes its extension points explicit:
- Adding a provider — implement a subclass of the base provider in
providers/, expose it fromproviders/__init__.py, and select it via the server's configuration. No changes toserver.pyormodels/are required. - Evolving the result schema — modify
models/search_result.pyand re-export frommodels/__init__.py; existing providers must be updated to populate any new required fields. - Sharing logic across providers — add helpers to
utils/and re-export them fromutils/__init__.pyso they can be imported uniformly. - Adding new tools — register additional MCP tools in
server.py; each new tool should still dispatch through a provider to preserve the layering.
By keeping the surface area small and the dependencies pointing inward (server → providers → models/utils), the project minimizes accidental coupling and keeps each module independently testable. Source: providers/base.py, Source: providers/__init__.py, Source: models/__init__.py, Source: utils/__init__.py
Source: https://github.com/jimmytbc/web_search_mcp / Human Manual
Search Fusion Pipeline and Provider Adapters
Related topics: Architecture and Module Layout, Fetch Tool, Deployment, and Operations
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Architecture and Module Layout, Fetch Tool, Deployment, and Operations
Search Fusion Pipeline and Provider Adapters
The Search Fusion Pipeline and Provider Adapters form the core retrieval layer of web_search_mcp. Their shared responsibility is to translate a single user query into one or more upstream search calls, normalize the heterogeneous responses, and merge them into a unified result set that downstream MCP tools can return. The providers/ package isolates vendor-specific concerns (Brave, Exa, Serper), while fusion/__init__.py exposes the merge/composition logic used by the tool layer in tools/search_web.py. Health and availability checks are kept separate in tools/search_health.py so that tool routing is not coupled to diagnostics.
1. Provider Adapter Layer
Each provider module under providers/ implements a thin, consistent interface that the fusion layer can invoke without knowing vendor semantics. Although the surface area is small, the adapters must respect each backend's authentication, query syntax, and response shape.
- Brave adapter (
providers/brave.py) wraps the Brave Search API. It is typically configured with an API key supplied via environment configuration and is expected to return a list of web results with title, URL, and snippet fields. Source: providers/brave.py. - Exa adapter (
providers/exa.py) wraps the Exa neural search API. Exa differs from keyword providers by supporting semantic queries and optional content extraction, so the adapter normalizes these into the sameResultenvelope used by the other providers. Source: providers/exa.py. - Serper adapter (
providers/serper.py) wraps the Serper (Google SERP) API. It translates the unified query into Serper'sqparameter and maps the JSONorganicarray back into the shared result schema. Source: providers/serper.py.
The shared contract typically includes: a constructor that takes credentials and configuration, an async search(query, **options) coroutine returning a list of normalized items, and graceful error handling that surfaces provider-specific failures as structured exceptions rather than raw HTTP errors.
2. Fusion Pipeline
The fusion/ package is the composition point where multiple provider outputs converge. fusion/__init__.py exposes the public entry points used by tools/search_web.py, including the function that orchestrates parallel provider calls and the function that deduplicates and ranks merged results.
The pipeline operates in three stages:
- Dispatch — Given a query and an ordered list of selected providers, the pipeline issues concurrent
search()coroutines and gathers their results. Failures from individual providers are isolated so one vendor outage does not break the whole call. - Normalization — Each provider returns items in its own dialect. The fusion layer coerces them into a uniform schema (consistent
title,url,snippet,provider,scorefields) so downstream code does not need provider-aware branches. - Merge and rank — Results from all providers are combined. Duplicates are collapsed using URL canonicalization, and surviving items are re-ranked — typically by interleaving or reciprocal-rank fusion — so the top-N output reflects cross-provider agreement rather than a single source's bias.
Source: fusion/__init__.py.
3. Tool Integration
The fusion pipeline is consumed by the MCP-facing tools rather than exposed directly. tools/search_web.py is the primary user-facing tool: it parses incoming MCP arguments, selects which providers to invoke (based on configuration or per-call overrides), delegates to fusion, and serializes the merged result back to the caller. Error envelopes from the fusion layer are mapped to MCP error responses. Source: tools/search_web.py.
tools/search_health.py provides a parallel diagnostic surface. It probes each provider adapter independently and reports reachability, latency, and credential validity. Because it bypasses the merge step, a provider that is slow or down in production can still be diagnosed without the fusion layer's parallelism masking the failure. Source: tools/search_health.py.
4. End-to-End Data Flow
The diagram below summarizes how a single MCP search_web call traverses the system from request to response.
flowchart LR
A[MCP Client] --> B[tools/search_web.py]
B --> C[fusion/__init__.py]
C --> P1[providers/brave.py]
C --> P2[providers/exa.py]
C --> P3[providers/serper.py]
P1 --> C
P2 --> C
P3 --> C
C --> D[Normalized Results]
D --> E[Merge + Rank]
E --> F[tools/search_web.py]
F --> A
H[tools/search_health.py] -.probe.-> P1
H -.probe.-> P2
H -.probe.-> P3The health tool (tools/search_health.py) runs along a side channel: it does not participate in result composition, so diagnostic traffic never pollutes user responses. Sources: tools/search_web.py, tools/search_health.py, fusion/__init__.py, providers/brave.py, providers/exa.py, providers/serper.py.
Design Notes
- Separation of concerns: Vendors live in
providers/, composition logic infusion/, and MCP-facing behavior intools/. This makes adding a fourth provider a localized change limited toproviders/plus a registration entry in the fusion dispatcher. Source: fusion/__init__.py. - Failure isolation: Because each provider call is dispatched independently, a single vendor error becomes a partial result rather than a total failure, which is critical for an MCP server exposed to multiple downstream agents. Source: tools/search_web.py.
- Schema uniformity: The
Resultenvelope produced after normalization is the contract between adapters and the merge step; any change to it is a coordinated change acrossproviders/andfusion/.
Source: https://github.com/jimmytbc/web_search_mcp / Human Manual
Fetch Tool, Deployment, and Operations
Related topics: Architecture and Module Layout, Search Fusion Pipeline and Provider Adapters
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Architecture and Module Layout, Search Fusion Pipeline and Provider Adapters
Fetch Tool, Deployment, and Operations
1. Scope and Role
The Fetch Tool is one of the MCP (Model Context Protocol) tools exposed by the web_search_mcp server. Its responsibility is to retrieve the contents of a remote URL on behalf of an MCP client and return a usable, text-oriented representation of the page (typically HTML converted to Markdown or plain text), suitable for downstream reasoning by an LLM. Alongside the upstream search tool, it forms the read-side of the project: search discovers candidate URLs, fetch materializes them. Source: tools/fetch_url.py
Operational concerns — packaging, runtime configuration, environment variables, and service orchestration — are handled through the repository's container assets and project metadata: Dockerfile, docker-compose.yml, .env.example, and pyproject.toml. Source: Dockerfile, docker-compose.yml, .env.example, pyproject.toml
2. Fetch Tool Implementation
2.1 Entry Point and Signature
The tool is registered as an MCP tool in tools/fetch_url.py. It is exposed under a stable name (commonly fetch_url) and accepts a URL plus optional parameters controlling content extraction behavior. Parameters typically include the target URL, an optional maximum length, and an optional rendering/extraction hint. Source: tools/fetch_url.py
2.2 Fetch and Extraction Pipeline
The implementation performs, conceptually, the following sequence:
- Validate and normalize the input URL.
- Check the local cache (see §3) before issuing any network request.
- Perform an HTTP
GETwith a configured user agent and timeout. - Convert the response body (HTML) into a compact Markdown representation so the result can be passed back to the model context without bloating it.
- Truncate to a configured character cap and return the payload along with metadata (final URL, status, length). Source: tools/fetch_url.py
This pipeline keeps the tool's behavior predictable for LLM consumers: bounded output size, deterministic text shape, and a single fetch attempt per call.
2.3 Error Handling
Network, HTTP-status, and parsing errors are surfaced as structured tool errors rather than raised exceptions, so the calling agent can decide whether to retry, fall back to the search tool, or report a failure. Source: tools/fetch_url.py
3. Fetch Caching Subsystem
utils/fetch_cache.py provides an on-disk cache that fronts the fetch tool. Its responsibilities are:
- Key derivation: build a stable cache key from the normalized URL and any request-shaping parameters that affect output (e.g., render mode, max length).
- Persistence: store the rendered Markdown body and metadata under that key in a local cache directory.
- TTL and invalidation: expire entries after a configurable time-to-live so cached content does not grow stale relative to the live web.
- Concurrency safety: serialize concurrent writes to the same key to avoid partially written cache files.
The cache layer is intentionally placed below the tool entry point: the tool calls the cache first, and only on a miss does it perform the actual HTTP fetch. Source: utils/fetch_cache.py
| Component | Layer | Responsibility |
|---|---|---|
fetch_url tool | MCP boundary | Parameter validation, result shaping |
fetch_cache | Below tool | Keying, TTL, disk persistence |
| HTTP client | Below cache | Network fetch and HTML → Markdown conversion |
4. Deployment and Operations
4.1 Container Image
Dockerfile defines the runtime image: a Python base image, installation of the project and its dependencies declared in pyproject.toml, and a default CMD that launches the MCP server (typically via a console-script entry such as web-search-mcp or an equivalent module invocation). The image is intended to be both the development and production artifact. Source: Dockerfile, pyproject.toml
4.2 Service Orchestration
docker-compose.yml wires the image into a single service. It commonly:
- Maps the container's listening port to a host port.
- Mounts the working directory or a named volume so the on-disk fetch cache (see §3) survives container restarts.
- Reads secrets and tunables from an
.envfile.
This makes docker compose up the canonical local-run command. Source: docker-compose.yml
4.3 Environment Configuration
.env.example enumerates the supported variables. The fetch tool typically relies on:
USER_AGENT— outbound HTTP user agent string.FETCH_TIMEOUT— per-request timeout in seconds.FETCH_MAX_LENGTH— output cap applied before returning to the model.FETCH_CACHE_DIRandFETCH_CACHE_TTL— cache location and freshness window.
Search-side and server-side variables (API keys, host/port, log level) are also defined here but are out of scope for the fetch tool. Source: .env.example
4.4 Project Metadata
pyproject.toml declares the package, its console scripts, and its runtime dependencies (HTTP client, HTML-to-Markdown converter, MCP server framework). It is the single source of truth for both pip install and the Docker build. Source: pyproject.toml
flowchart LR
Client[MCP Client / Agent] -->|fetch_url call| Tool[fetch_url.py]
Tool -->|lookup| Cache[fetch_cache.py]
Cache -->|miss| HTTP[HTTP GET + HTML→MD]
HTTP --> Cache
Cache --> Tool
Tool --> Client
Env[.env] --> Tool
Env --> Cache
Compose[docker-compose.yml] --> Dockerfile
Dockerfile --> Container[MCP Server Container]
Container --> Tool5. Operational Notes
- Output size budget:
FETCH_MAX_LENGTHis the primary knob for keeping fetch responses inside the model's context window. Source: .env.example - Cache durability: because the cache lives on disk, mounting a volume in
docker-compose.ymlis what preserves warm caches across restarts. Source: docker-compose.yml - Reproducible builds:
pyproject.tomlpins the dependency set consumed by theDockerfile, so image builds are deterministic given the lock state. Source: pyproject.toml, Dockerfile
Source: https://github.com/jimmytbc/web_search_mcp / Human Manual
Doramagic Pitfall Log
Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
Doramagic Pitfall Log
Found 6 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Capability evidence risk - Capability evidence risk requires verification.
1. Capability evidence risk: Capability evidence risk requires verification
- Severity: medium
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: capability.assumptions | https://github.com/jimmytbc/web_search_mcp
2. Maintenance risk: Maintenance risk requires verification
- Severity: medium
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://github.com/jimmytbc/web_search_mcp
3. Security or permission risk: Security or permission risk requires verification
- Severity: medium
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: downstream_validation.risk_items | https://github.com/jimmytbc/web_search_mcp
4. Security or permission risk: Security or permission risk requires verification
- Severity: medium
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: risks.scoring_risks | https://github.com/jimmytbc/web_search_mcp
5. Maintenance risk: Maintenance risk requires verification
- Severity: low
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://github.com/jimmytbc/web_search_mcp
6. Maintenance risk: Maintenance risk requires verification
- Severity: low
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://github.com/jimmytbc/web_search_mcp
Source: Doramagic discovery, validation, and Project Pack records
Community Discussion Evidence
These external discussion links are review inputs, not standalone proof that the project is production-ready.
Count of project-level external discussion links exposed on this manual page.
Open the linked issues or discussions before treating the pack as ready for your environment.
Community Discussion Evidence
Doramagic exposes project-level community discussion separately from official documentation. Review these links before using web_search_mcp with real data or production workflows.
- Capability evidence risk requires verification - GitHub / issue
Source: Project Pack community evidence and pitfall evidence