Skip to main content
Glama
n24q02m

WET - Web Extended Toolkit

config

Idempotent

Manage server configuration and operations including status checks, settings adjustments, cache clearing, and documentation reindexing for the WET Web Extended Toolkit.

Instructions

Server config and management. Actions: status|set|cache_clear|docs_reindex. Use help tool with tool_name='config' for full docs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYes
keyNo
valueNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The config tool handler function. Implements actions: status (shows server configuration and status), set (updates runtime settings like log_level, tool_timeout, etc.), cache_clear (clears web cache), and docs_reindex (forces re-index of a library). Returns JSON responses for all actions.
    async def config(
        action: str,
        key: str | None = None,
        value: str | None = None,
    ) -> str:
        """Server configuration and management.
    
        Actions:
        - status: Show current config and status
        - set: Update runtime setting (key + value required)
        - cache_clear: Clear web cache
        - docs_reindex: Force re-index a library (key = library name)
        """
        match action:
            case "status":
                from wet_mcp.embedder import get_backend
                from wet_mcp.reranker import get_reranker
    
                embed_backend = get_backend()
                reranker = get_reranker()
    
                status = {
                    "database": {
                        "path": str(settings.get_db_path()),
                        "docs_indexed": (_docs_db.stats() if _docs_db else {}),
                    },
                    "embedding": {
                        "backend": (
                            type(embed_backend).__name__ if embed_backend else None
                        ),
                        "dims": _embedding_dims,
                        "available": embed_backend is not None,
                    },
                    "reranker": {
                        "available": reranker is not None,
                        "backend": (type(reranker).__name__ if reranker else None),
                    },
                    "cache": {
                        "enabled": settings.wet_cache,
                        "path": (
                            str(settings.get_cache_db_path())
                            if settings.wet_cache
                            else None
                        ),
                    },
                    "sync": {
                        "enabled": settings.sync_enabled,
                        "remote": settings.sync_remote,
                        "folder": settings.sync_folder,
                        "interval": settings.sync_interval,
                    },
                    "settings": {
                        "log_level": settings.log_level,
                        "tool_timeout": settings.tool_timeout,
                    },
                }
                return json.dumps(status, indent=2, default=str)
    
            case "set":
                if not key or value is None:
                    return json.dumps({"error": "key and value are required for set"})
                valid_keys = {
                    "log_level",
                    "tool_timeout",
                    "wet_cache",
                    "sync_enabled",
                    "sync_remote",
                    "sync_folder",
                    "sync_interval",
                }
                if key not in valid_keys:
                    return json.dumps(
                        {
                            "error": f"Invalid key: {key}",
                            "valid_keys": sorted(valid_keys),
                        }
                    )
                if key == "log_level":
                    settings.log_level = value.upper()
                    logger.remove()
                    logger.add(sys.stderr, level=settings.log_level)
                elif key == "tool_timeout":
                    settings.tool_timeout = int(value)
                elif key == "wet_cache":
                    settings.wet_cache = value.lower() in (
                        "true",
                        "1",
                        "yes",
                    )
                elif key == "sync_enabled":
                    settings.sync_enabled = value.lower() in (
                        "true",
                        "1",
                        "yes",
                    )
                elif key == "sync_interval":
                    settings.sync_interval = int(value)
                else:
                    setattr(settings, key, value)
                return json.dumps(
                    {
                        "status": "updated",
                        "key": key,
                        "value": getattr(settings, key),
                    },
                    default=str,
                )
    
            case "cache_clear":
                if _web_cache:
                    _web_cache.clear()
                    return json.dumps({"status": "cache cleared"})
                return json.dumps({"error": "Cache is not enabled"})
    
            case "docs_reindex":
                if not key:
                    return json.dumps({"error": "key (library name) is required"})
                if not _docs_db:
                    return json.dumps({"error": "Docs database not initialized"})
                lib = _docs_db.get_library(key)
                if lib:
                    ver = _docs_db.get_best_version(lib["id"])
                    if ver:
                        _docs_db.clear_version_chunks(ver["id"])
                    return json.dumps(
                        {
                            "status": "cleared",
                            "library": key,
                            "hint": ("Next docs search will re-index"),
                        }
                    )
                return json.dumps({"error": f"Library '{key}' not found in index"})
    
            case _:
                return json.dumps(
                    {
                        "error": f"Unknown action: {action}",
                        "valid_actions": [
                            "status",
                            "set",
                            "cache_clear",
                            "docs_reindex",
                        ],
                    }
                )
  • Registration of the config tool using @mcp.tool() decorator. Defines tool metadata including description, title, and ToolAnnotations (readOnlyHint=False, destructiveHint=False, idempotentHint=True, openWorldHint=False).
    @mcp.tool(
        description=(
            "Server config and management. Actions: "
            "status|set|cache_clear|docs_reindex. "
            "Use help tool with tool_name='config' for full docs."
        ),
        annotations=ToolAnnotations(
            title="Config",
            readOnlyHint=False,
            destructiveHint=False,
            idempotentHint=True,
            openWorldHint=False,
        ),
    )
  • The Settings class defines all configuration parameters that the config tool can read and modify. Includes settings for SearXNG, crawler, tool timeout, embedding/reranking backends, cache, docs storage, sync, and logging. Provides methods to resolve and validate settings.
    class Settings(BaseSettings):
        """WET MCP Server configuration.
    
        Environment variables:
        - SEARXNG_URL: SearXNG instance URL (default: http://localhost:8080)
        - API_KEYS: Provider API keys, supports multiple providers
            Format: "ENV_VAR:key,ENV_VAR:key,..."
            Or file path: "@path/to/keys"
            Example: "GOOGLE_API_KEY:AIza...,COHERE_API_KEY:..."
            Embedding providers: Google, OpenAI, Cohere
            Reranking providers: Cohere (auto-detected)
        - LITELLM_PROXY_URL: LiteLLM Proxy base URL (e.g. http://10.0.0.20:4000)
        - LITELLM_PROXY_KEY: API key for the LiteLLM Proxy
        - EMBEDDING_API_BASE: Custom embedding endpoint URL
        - EMBEDDING_API_KEY: API key for custom embedding endpoint
        - RERANK_API_BASE: Custom rerank endpoint URL
        - RERANK_API_KEY: API key for custom rerank endpoint
        - LLM_API_BASE: Custom LLM chat completion endpoint URL
        - LLM_API_KEY: API key for custom LLM endpoint
        - EMBEDDING_MODEL: LiteLLM embedding model (auto-detected if not set)
        - EMBEDDING_DIMS: Embedding dimensions (0 = auto-detect, default 768)
        - EMBEDDING_BACKEND: "litellm" | "local" (auto: API_KEYS -> litellm, else local)
            Local: GGUF if GPU + llama-cpp-python, else ONNX
        - RERANK_ENABLED: Enable reranking (default: true)
        - RERANK_BACKEND: "litellm" | "local" (auto: Cohere key -> litellm, else local)
        - RERANK_MODEL: LiteLLM rerank model (auto-detected from API_KEYS if Cohere)
        - RERANK_TOP_N: Return top N results after reranking (default: 10)
        - SYNC_ENABLED: Enable rclone sync (default: false)
        - SYNC_PROVIDER: rclone provider type (default: "drive" for Google Drive)
        - SYNC_REMOTE: Rclone remote name (default: "gdrive")
        - SYNC_FOLDER: Remote folder name (default: "wet-mcp")
        - SYNC_INTERVAL: Auto-sync interval in seconds (default: 300)
    
        LiteLLM Mode Detection (resolve_litellm_mode):
        - "proxy": LITELLM_PROXY_URL is set → all calls routed through proxy
        - "sdk": API_KEYS or custom endpoints set → direct LiteLLM SDK calls
        - "local": no keys/proxy → local ONNX models only
        """
    
        # SearXNG
        searxng_url: str = "http://localhost:41592"
        searxng_timeout: int = 30
    
        # Crawler
        crawler_headless: bool = True
        crawler_timeout: int = 60
    
        # SearXNG Management
        wet_auto_searxng: bool = True
        wet_searxng_port: int = 41592
    
        # Tool execution timeout (seconds, 0 = no timeout)
        tool_timeout: int = 120
    
        # Media
        download_dir: str = "~/.wet-mcp/downloads"
    
        # Media Analysis (LiteLLM)
        api_keys: SecretStr | None = None  # ENV_VAR:key,ENV_VAR:key (multiple providers)
    
        # LiteLLM Proxy (selfhosted gateway)
        litellm_proxy_url: str = ""  # e.g. http://10.0.0.20:4000
        litellm_proxy_key: SecretStr | None = None
    
        # Custom endpoints (e.g. modalcom-ai-workers on Modal.com)
        embedding_api_base: str = ""
        embedding_api_key: SecretStr | None = None
        rerank_api_base: str = ""
        rerank_api_key: SecretStr | None = None
        llm_api_base: str = ""
        llm_api_key: SecretStr | None = None
    
        llm_models: str = "gemini/gemini-3-flash-preview"  # provider/model (fallback chain)
        llm_temperature: float | None = None
    
        # Cache (web operations)
        wet_cache: bool = True  # Enable/disable web cache
        cache_dir: str = ""  # Cache database directory, default: ~/.wet-mcp
    
        # Docs storage
        docs_db_path: str = ""  # Default: ~/.wet-mcp/docs.db
    
        # Embedding
        embedding_model: str = ""  # LiteLLM format, auto-detect if empty
        embedding_dims: int = 0  # 0 = use server default (768)
        embedding_backend: str = (
            ""  # "litellm" | "local" | "" (auto: API_KEYS->litellm, else local)
        )
    
        # Reranking
        rerank_enabled: bool = (
            True  # Enable reranking (always available via local fallback)
        )
        rerank_backend: str = (
            ""  # "litellm" | "local" | "" (auto: Cohere->litellm, else local)
        )
        rerank_model: str = (
            ""  # LiteLLM rerank model (e.g., "cohere/rerank-multilingual-v3.0")
        )
        rerank_top_n: int = 10  # Return top N after reranking
    
        # Docs sync (rclone)
        sync_enabled: bool = False
        sync_provider: str = "drive"  # rclone provider type (drive, dropbox, s3, etc.)
        sync_remote: str = "gdrive"  # rclone remote name
        sync_folder: str = "wet-mcp"  # remote folder
        sync_interval: int = 300  # seconds, 0 = manual only
    
        # Logging
        log_level: str = "INFO"
    
        model_config = {"env_prefix": "", "case_sensitive": False}
    
        # --- Path helpers (aligned with mnemo-mcp) ---
    
        def get_data_dir(self) -> Path:
            """Get data directory.
    
            Uses CACHE_DIR if set, otherwise ~/.wet-mcp/.
            """
            if self.cache_dir:
                return Path(self.cache_dir).expanduser()
            return _default_data_dir()
    
        def get_db_path(self) -> Path:
            """Get resolved docs database path."""
            if self.docs_db_path:
                return Path(self.docs_db_path).expanduser()
            return self.get_data_dir() / "docs.db"
    
        def get_cache_db_path(self) -> Path:
            """Get resolved web cache database path."""
            return self.get_data_dir() / "cache.db"
    
        # --- API key management ---
    
        # LiteLLM uses different env vars for embeddings vs completions
        _ENV_ALIASES: dict[str, str] = {
            "GOOGLE_API_KEY": "GEMINI_API_KEY",
        }
    
        def setup_api_keys(self) -> dict[str, list[str]]:
            """Parse API_KEYS and set env vars for LiteLLM.
    
            Format: "GOOGLE_API_KEY:AIza...,OPENAI_API_KEY:sk-..."
            Or file: "@path/to/keys_file"
    
            Also sets aliases (e.g., GOOGLE_API_KEY -> GEMINI_API_KEY)
            because LiteLLM embedding uses GEMINI_API_KEY for gemini/ models.
    
            Returns:
                Dict mapping env var name to list of API keys.
            """
            if not self.api_keys:
                return {}
    
            keys_by_env: dict[str, list[str]] = {}
            api_keys_str = self.api_keys.get_secret_value()
    
            # Handle file-based keys
            if api_keys_str.startswith("@"):
                path_str = api_keys_str[1:]
                path = Path(path_str).expanduser()
                if not path.exists():
                    raise FileNotFoundError(f"API keys file not found: {path}")
    
                try:
                    # Read content and normalize newlines to commas
                    content = path.read_text(encoding="utf-8").strip()
                    api_keys_str = content.replace("\n", ",")
                except Exception as e:
                    raise ValueError(f"Failed to read API keys file: {e}") from e
    
            for pair in api_keys_str.split(","):
                pair = pair.strip()
                if ":" not in pair:
                    continue
    
                env_var, key = pair.split(":", 1)
                env_var = env_var.strip()
                key = key.strip()
    
                if not key:
                    continue
    
                keys_by_env.setdefault(env_var, []).append(key)
    
            # Set first key of each env var (LiteLLM reads from env)
            for env_var, keys in keys_by_env.items():
                if keys:
                    os.environ[env_var] = keys[0]
                    # Set alias if defined (e.g., GOOGLE_API_KEY -> GEMINI_API_KEY)
                    alias = self._ENV_ALIASES.get(env_var)
                    if alias and alias not in os.environ:
                        os.environ[alias] = keys[0]
    
            return keys_by_env
    
        # --- Embedding resolution ---
    
        def resolve_embedding_model(self) -> str | None:
            """Return explicit EMBEDDING_MODEL or None for auto-detect."""
            if self.embedding_model:
                return self.embedding_model
            return None
    
        def resolve_embedding_dims(self) -> int:
            """Return explicit EMBEDDING_DIMS or 0 for auto-detect."""
            return self.embedding_dims
    
        def resolve_local_embedding_model(self) -> str:
            """Resolve local embedding model: GGUF if GPU + llama-cpp, else ONNX."""
            return _resolve_local_model(
                "n24q02m/Qwen3-Embedding-0.6B-ONNX",
                "n24q02m/Qwen3-Embedding-0.6B-GGUF",
            )
    
        def resolve_embedding_backend(self) -> str:
            """Resolve embedding backend: 'local' or 'litellm'.
    
            Always returns a valid backend (never empty).
    
            Auto-detect order:
            1. Explicit EMBEDDING_BACKEND setting
            2. 'litellm' if in proxy or sdk mode
            3. 'local' (qwen3-embed built-in, always available)
            """
            if self.embedding_backend:
                return self.embedding_backend
            mode = self.resolve_litellm_mode()
            if mode in ("proxy", "sdk"):
                return "litellm"
            return "local"
    
        # --- Reranking resolution ---
    
        def resolve_local_rerank_model(self) -> str:
            """Resolve local rerank model: GGUF if GPU + llama-cpp, else ONNX."""
            return _resolve_local_model(
                "n24q02m/Qwen3-Reranker-0.6B-ONNX",
                "n24q02m/Qwen3-Reranker-0.6B-GGUF",
            )
    
        def resolve_rerank_backend(self) -> str:
            """Resolve reranking backend: 'local', 'litellm', or ''.
    
            Returns '' only if reranking is explicitly disabled.
            Always returns a valid backend otherwise.
    
            Auto-detect order:
            1. Explicit RERANK_BACKEND setting
            2. 'litellm' if proxy mode (proxy handles routing)
            3. 'litellm' if RERANK_MODEL is set
            4. 'litellm' if API_KEYS contains a rerank-capable provider (Cohere)
            5. 'local' (qwen3-embed built-in, always available)
            """
            if not self.rerank_enabled:
                return ""
            if self.rerank_backend:
                return self.rerank_backend
            # Proxy mode: always litellm (proxy handles routing)
            if self.litellm_proxy_url:
                return "litellm"
            if self.rerank_model:
                return "litellm"
    
            # Check env vars first (populated by setup_api_keys)
            for provider_key in _RERANK_PROVIDERS:
                if os.environ.get(provider_key):
                    return "litellm"
    
            if self.api_keys:
                val = self.api_keys.get_secret_value()
                if not val.startswith("@"):
                    for provider_key in _RERANK_PROVIDERS:
                        if provider_key in val:
                            return "litellm"
            return "local"
    
        def resolve_rerank_model(self) -> str | None:
            """Resolve rerank model from config or auto-detect from API_KEYS."""
            if self.rerank_model:
                return self.rerank_model
    
            # Check env vars first
            for provider_key, model in _RERANK_PROVIDERS.items():
                if os.environ.get(provider_key):
                    return model
    
            if self.api_keys:
                val = self.api_keys.get_secret_value()
                if not val.startswith("@"):
                    for provider_key, model in _RERANK_PROVIDERS.items():
                        if provider_key in val:
                            return model
            return None
    
        # --- LiteLLM mode resolution ---
    
        def resolve_litellm_mode(self) -> str:
            """Detect LiteLLM mode: 'proxy', 'sdk', or 'local'."""
            if self.litellm_proxy_url:
                return "proxy"
            if (
                self.api_keys
                or self.embedding_api_base
                or self.rerank_api_base
                or self.llm_api_base
            ):
                return "sdk"
            return "local"
    
        def setup_litellm(self) -> str:
            """One-time LiteLLM configuration. Call once during lifespan startup.
    
            Returns mode string: 'proxy', 'sdk', or 'local'.
            """
            mode = self.resolve_litellm_mode()
    
            if mode == "proxy":
                import litellm
    
                os.environ["LITELLM_PROXY_API_BASE"] = self.litellm_proxy_url
                os.environ["LITELLM_PROXY_API_KEY"] = (
                    self.litellm_proxy_key.get_secret_value()
                    if self.litellm_proxy_key
                    else ""
                )
                litellm.use_litellm_proxy = True
                logger.info(f"LiteLLM Proxy mode: {self.litellm_proxy_url}")
            elif mode == "sdk":
                self.setup_api_keys()
                logger.info("LiteLLM SDK direct mode")
            else:
                logger.info("Local mode (no LiteLLM)")
    
            return mode
    
        def get_embedding_litellm_kwargs(self) -> dict:
            """Get extra kwargs for litellm embedding calls (api_base, api_key for Mode 2b)."""
            kwargs = {}
            if self.embedding_api_base:
                kwargs["api_base"] = self.embedding_api_base
            if self.embedding_api_key:
                kwargs["api_key"] = self.embedding_api_key.get_secret_value()
            return kwargs
    
        def get_rerank_litellm_kwargs(self) -> dict:
            """Get extra kwargs for litellm rerank calls."""
            kwargs = {}
            if self.rerank_api_base:
                kwargs["api_base"] = self.rerank_api_base
            if self.rerank_api_key:
                kwargs["api_key"] = self.rerank_api_key.get_secret_value()
            return kwargs
    
        def get_llm_litellm_kwargs(self) -> dict:
            """Get extra kwargs for litellm chat completion calls."""
            kwargs = {}
            if self.llm_api_base:
                kwargs["api_base"] = self.llm_api_base
            if self.llm_api_key:
                kwargs["api_key"] = self.llm_api_key.get_secret_value()
            return kwargs
    
    
    settings = Settings()
  • Input parameters for the config tool: action (required string), key (optional string for set/docs_reindex actions), and value (optional string for set action). The function signature defines the tool's input schema.
        action: str,
        key: str | None = None,
        value: str | None = None,
    ) -> str:
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds minimal behavioral context beyond annotations. Annotations already provide readOnlyHint=false, destructiveHint=false, idempotentHint=true, and openWorldHint=false. The description mentions specific actions (status|set|cache_clear|docs_reindex) which gives some operational context, but doesn't elaborate on what these actions do, their side effects, or any rate limits. No contradiction with annotations exists.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately concise with two sentences. The first sentence establishes purpose and lists actions, while the second directs to additional documentation. There's no wasted language, though it could be more front-loaded with clearer purpose. The structure is logical but minimal.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 3 parameters with 0% schema coverage, annotations covering basic behavioral hints, and an output schema exists, the description is moderately complete. It covers the basic purpose and action values but lacks parameter details, usage context, and behavioral specifics. The existence of an output schema reduces the need to describe return values, but more operational context would be helpful.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage and 3 parameters (action, key, value), the description provides almost no parameter information. It mentions action values (status|set|cache_clear|docs_reindex) which helps clarify the action parameter, but doesn't explain key/value parameters or their relationships. The description doesn't compensate for the complete lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states 'Server config and management' which provides a general purpose, but it's vague about what specific resources are being configured. It lists actions (status|set|cache_clear|docs_reindex) which helps clarify scope, but doesn't specify what type of server or configuration is involved. The description distinguishes from siblings by mentioning the help tool, but doesn't clearly differentiate config from other tools like extract or search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It mentions using the help tool for full documentation, but doesn't specify use cases, prerequisites, or when other tools might be more appropriate. There's no mention of when to choose config over other sibling tools like extract or search for related tasks.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/n24q02m/wet-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server