[project] name = "wet-mcp" version = "3.14.9" description = "Open-source MCP server for AI agents: web search, content extraction, and library docs." readme = "README.md" license = { text = "Apache-2.0" } authors = [{ name = "n24q02m", email = "quangminh2422004@gmail.com" }] keywords = ["mcp", "mcp-server", "model-context-protocol", "web-scraping", "searxng", "crawl4ai", "library-docs", "claude", "cursor", "copilot", "antigravity", "codex", "opencode"] classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Scientific/Engineering :: Artificial Intelligence", ] requires-python = "==3.13.*" dependencies = [ # Shared web infrastructure (SSRF, SearXNG, URL utils, ScrapingAgent + Crawl4AI transitive). # wet consumes web-core's SSRF surface (is_safe_url / safe_httpx_client) AND, since 2.3.0, # the remote render backends (CFBrowserRenderingClient / BrowserlessClient / # RemoteRenderStrategy) for the browser provider chain + the under-rendered escalation fix. "n24q02m-web-core>=2.5.1,<3", # MCP Server "mcp[cli]>=1.29.1,<2", # HTTP Client "httpx", # Config # Pydantic upper bound predates the litellm-passthrough migration: it was # required by the now-removed direct cohere SDK dep (cohere>=6.1.0 pinned # pydantic-core <2.44 while pydantic 2.13.x ships pydantic-core 2.46). # Kept conservatively; relax in a dedicated bump. "pydantic>=2.13.4,<2.14", "pydantic-settings", # Logging "loguru", # LLM + Embedding + Reranking dispatch through mcp_core.llm (litellm # passthrough via the mcp-core[llm] extra below). No direct provider SDK # deps; the openai SDK remains transitive via litellm. "httpx>=0.28.1", # Vector search for docs "sqlite-vec", # Local ONNX embedding + reranking (auto-fallback when no cloud API) "fastretrieval>=1.1.0,<2", "pillow>=12.3.0", "diskcache>=5.6.3", "cryptography>=50.0.0", # Document conversion (PDF, DOCX, PPTX → Markdown) "markitdown[pdf,docx,pptx,xlsx]", # Schema validation for structured extraction "jsonschema>=4.26.0", # Per-domain rate limiting for batch operations "aiolimiter>=1.2.1", # 1.19.0 ships mcp_core.cli's argument-spec extra subcommand support; # 1.20.0b2 keys build_cli's credential store by plugin slug; 1.21.0 adds # RFC 9207 `iss` and fail-closed authorization-code minting; 1.22.0 ships # the shared D1 + Vectorize clients; 1.24.0b1 separates OAuth JWT signing # from CREDENTIAL_SECRET so tokens can be revoked without rekeying per-user # vault data or changing stable subject IDs. "n24q02m-mcp-core[llm]==1.24.6", # Schema migrations (auto-migrate-on-startup with backup) "alembic>=1.18.5", # fastmcp arrives transitively via mcp-core; both bounds are pinned here, # for different reasons. # Floor: keeps a Renovate lockFileMaintenance refresh from resolving back # to fastmcp 2.x, which carries 5 CVEs including a critical SSRF. # Ceiling: fastmcp 4 moves the runtime onto fastmcp-slim and the protocol # library from the MCP Python SDK v1 to v2 (`mcp>=2,<3`), where `mcp.types` # is a re-export of the separate `mcp-types` package and its models rename # every field to snake_case, keeping the camelCase spellings only as # Pydantic aliases. The published mcp-core 1.24.0b1 does not yet carry a # compatible ceiling, so preserve the SDK 1 / FastMCP 3 boundary here. # Lift both bounds together only after the shared core and this server's # imported FastMCP/ToolAnnotations surfaces migrate. "fastmcp>=3.4.7,<5", # Google Drive sync "google-api-python-client>=2.198.0", "google-auth>=2.56.2", # S3-compatible sync (AWS S3, Cloudflare R2, Backblaze B2, MinIO) "boto3>=1.43.56", # Production WSGI server for embedded SearXNG on Windows # (Werkzeug dev server deadlocks under concurrent engine requests) "waitress>=3.0.2; sys_platform == 'win32'", # greenlet 3.5.0 (pulled transitively via patchright) ships no # manylinux_*_aarch64 wheels and has no source dist, so the Docker # arm64 build fails. Cap below until upstream re-publishes arm64. "greenlet<3.5.6", # urllib3 floor patches 2 HIGH CVEs (transitive via boto3/botocore, # requests, google-api-python-client): GHSA-qccp-gfcp-xxvc (sensitive # headers forwarded across origins in proxied low-level redirects) + # GHSA-mf9v-mfxr-j63j (decompression-bomb safeguards bypassed in parts # of the streaming API). Both first patched in urllib3 2.7.0. "urllib3>=2.7.0", # langsmith floor patches HIGH CVE GHSA-3644-q5cj-c5c7 (public prompt # pull deserializes untrusted manifests without trust boundary # warning). Reachable transitively via web-core -> langgraph -> # langchain-core -> langsmith (used for tracing, not prompt pull, so # the vulnerable Client.pull_prompt path is not exercised by wet-mcp # — pinned as defense-in-depth). First patched in langsmith 0.8.0. "langsmith>=0.10.10", ] [dependency-groups] dev = [ "pytest", "pytest-asyncio", "pytest-timeout", "ruff", "ty", "pre-commit", "pytest-cov>=7.1.0", "pytest-xdist>=3.8.0", "syrupy>=5.5.3", "moto[s3]>=5.2.2", ] [project.scripts] wet = "wet_mcp.cli:main" wet-mcp = "wet_mcp.cli:main" # legacy command alias (uvx wet-mcp configs) [project.urls] Homepage = "https://github.com/n24q02m/wet" Repository = "https://github.com/n24q02m/wet.git" Issues = "https://github.com/n24q02m/wet/issues" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/wet_mcp"] artifacts = ["src/wet_mcp/data/**/*.json"] [tool.hatch.build.targets.wheel.sources] "src" = "" [tool.hatch.build.targets.sdist] include = [ "src/wet_mcp/**/*.py", "src/wet_mcp/**/*.yml", "src/wet_mcp/**/*.md", "src/wet_mcp/**/*.ini", "src/wet_mcp/**/*.mako", "src/wet_mcp/alembic/README", ] [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" testpaths = ["tests"] python_files = ["test_*.py"] timeout = 30 markers = [ "integration: integration tests (require network/services)", "live: live MCP protocol tests (spawn real server via stdio)", "network: tests requiring network access", "full: full live tests with real services", "e2e: end-to-end tests (3 setup modes: relay, env, plugin)", "slow: slow tests (crawl, download, etc.)", ] addopts = "-m 'not integration and not live and not full and not e2e'" [tool.ty] rules = { unresolved-import = "ignore", unresolved-attribute = "ignore", possibly-missing-attribute = "ignore", pydantic-discarded-extra-argument = "ignore", unused-ignore-comment = "ignore", unused-type-ignore-comment = "ignore" } [tool.semantic_release] version_toml = ["pyproject.toml:project.version"] version_variables = [".claude-plugin/plugin.json:version", "server.json:version"] tag_format = "v{version}" commit_message = "chore(release): v{version}" major_on_zero = false [tool.semantic_release.changelog] changelog_file = "CHANGELOG.md" [tool.semantic_release.remote] type = "github" [tool.ruff] line-length = 88 target-version = "py313" [tool.ruff.lint] select = ["E", "F", "W", "I", "UP", "B", "C4"] ignore = ["E501"] [tool.ruff.format] quote-style = "double" [tool.coverage.run] source = ["src/wet_mcp"] branch = true [tool.coverage.report] # Phase 2 baseline: 92% (was 93% post-Phase-1). Phase 2 ships ~600 new # lines (alembic runner + tier1 warmup + project_lock + new search # dispatchers + ingest_tier2) — coverage climbs back to >=93% as the # pre-existing _do_docs_search fallback path lines (server.py 2025-2029, # 2061-2080, 2213-2256, 2277-2301) get fixture coverage in the # consolidated pre-release session per user "test+fix 1 lượt cuối" # strategy. Spec section 3 long-term target stays at >=95%. fail_under = 92 show_missing = true exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:"] [tool.uv] # crawl4ai requires `unclecode-litellm==1.81.13`, a fork that ships the same # top-level `litellm` package as the upstream litellm this project already # depends on through mcp-core[llm]. Installing both writes two different # versions into one site-packages/litellm/, and which files survive is decided # by install order — CI has already failed on a run where the fork landed last: # ImportError: cannot import name '_redact_string' from 'litellm._logging' # Drop the fork so exactly one litellm is installed. crawl4ai imports litellm # only inside its LLM helper functions, which this project never calls (it uses # AsyncWebCrawler/BrowserConfig/CrawlerRunConfig), and mcp_core.llm needs the # upstream 1.9x API. override-dependencies = ["unclecode-litellm ; sys_platform == 'unreachable'"] [tool.uv.sources]