dhole-mcp 14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dhole_mcp-14.0/.gitattributes +21 -0
- dhole_mcp-14.0/.github/workflows/lint.yml +29 -0
- dhole_mcp-14.0/.github/workflows/test.yml +41 -0
- dhole_mcp-14.0/.gitignore +76 -0
- dhole_mcp-14.0/CHANGELOG.md +139 -0
- dhole_mcp-14.0/CONTRIBUTING.md +80 -0
- dhole_mcp-14.0/LICENSE +22 -0
- dhole_mcp-14.0/NOTICE.ddgs.txt +38 -0
- dhole_mcp-14.0/PKG-INFO +153 -0
- dhole_mcp-14.0/README.md +99 -0
- dhole_mcp-14.0/docs/flow.svg +63 -0
- dhole_mcp-14.0/docs/hound-hero.png +0 -0
- dhole_mcp-14.0/docs/hound-logo.png +0 -0
- dhole_mcp-14.0/docs/hound-scene.png +0 -0
- dhole_mcp-14.0/docs/tokens.svg +36 -0
- dhole_mcp-14.0/package.json +6 -0
- dhole_mcp-14.0/pyproject.toml +127 -0
- dhole_mcp-14.0/src/dhole_mcp/__init__.py +61 -0
- dhole_mcp-14.0/src/dhole_mcp/__main__.py +4 -0
- dhole_mcp-14.0/src/dhole_mcp/actions.py +142 -0
- dhole_mcp-14.0/src/dhole_mcp/browser.py +1347 -0
- dhole_mcp-14.0/src/dhole_mcp/cache.py +221 -0
- dhole_mcp-14.0/src/dhole_mcp/cli.py +105 -0
- dhole_mcp-14.0/src/dhole_mcp/cli_ui.py +212 -0
- dhole_mcp-14.0/src/dhole_mcp/crawl.py +864 -0
- dhole_mcp-14.0/src/dhole_mcp/envelope.py +417 -0
- dhole_mcp-14.0/src/dhole_mcp/errors.py +132 -0
- dhole_mcp-14.0/src/dhole_mcp/extractor.py +49 -0
- dhole_mcp-14.0/src/dhole_mcp/feed.py +161 -0
- dhole_mcp-14.0/src/dhole_mcp/fetcher.py +592 -0
- dhole_mcp-14.0/src/dhole_mcp/focus.py +199 -0
- dhole_mcp-14.0/src/dhole_mcp/links.py +184 -0
- dhole_mcp-14.0/src/dhole_mcp/metadata.py +181 -0
- dhole_mcp-14.0/src/dhole_mcp/ocr.py +252 -0
- dhole_mcp-14.0/src/dhole_mcp/parse.py +236 -0
- dhole_mcp-14.0/src/dhole_mcp/pdf_extractor.py +741 -0
- dhole_mcp-14.0/src/dhole_mcp/reddit.py +191 -0
- dhole_mcp-14.0/src/dhole_mcp/reranker.py +331 -0
- dhole_mcp-14.0/src/dhole_mcp/search.py +1136 -0
- dhole_mcp-14.0/src/dhole_mcp/search_engines.py +240 -0
- dhole_mcp-14.0/src/dhole_mcp/search_metasearch.py +1082 -0
- dhole_mcp-14.0/src/dhole_mcp/search_proxy.py +302 -0
- dhole_mcp-14.0/src/dhole_mcp/security.py +561 -0
- dhole_mcp-14.0/src/dhole_mcp/server.py +3620 -0
- dhole_mcp-14.0/src/dhole_mcp/sitemap.py +246 -0
- dhole_mcp-14.0/src/dhole_mcp/structured.py +322 -0
- dhole_mcp-14.0/src/dhole_mcp/trafilatura_extractor.py +353 -0
- dhole_mcp-14.0/src/dhole_mcp/updater.py +668 -0
- dhole_mcp-14.0/src/hound_mcp/__init__.py +39 -0
- dhole_mcp-14.0/src/hound_mcp/__main__.py +4 -0
- dhole_mcp-14.0/tests/__init__.py +1 -0
- dhole_mcp-14.0/tests/background_checks.pdf +0 -0
- dhole_mcp-14.0/tests/brute_verify.py +116 -0
- dhole_mcp-14.0/tests/conftest.py +67 -0
- dhole_mcp-14.0/tests/dummy.pdf +0 -0
- dhole_mcp-14.0/tests/e2e_mcp_test.py +219 -0
- dhole_mcp-14.0/tests/fetcher_redirect_test.py +130 -0
- dhole_mcp-14.0/tests/old_reddit_real.html +148 -0
- dhole_mcp-14.0/tests/sample.docx +0 -0
- dhole_mcp-14.0/tests/sample.xlsx +0 -0
- dhole_mcp-14.0/tests/test_audit_fixes.py +206 -0
- dhole_mcp-14.0/tests/test_cache.py +175 -0
- dhole_mcp-14.0/tests/test_cli.py +135 -0
- dhole_mcp-14.0/tests/test_envelope.py +312 -0
- dhole_mcp-14.0/tests/test_errors.py +252 -0
- dhole_mcp-14.0/tests/test_extractor.py +70 -0
- dhole_mcp-14.0/tests/test_feed.py +101 -0
- dhole_mcp-14.0/tests/test_fetcher.py +233 -0
- dhole_mcp-14.0/tests/test_focus.py +214 -0
- dhole_mcp-14.0/tests/test_hardening_regressions.py +103 -0
- dhole_mcp-14.0/tests/test_links.py +167 -0
- dhole_mcp-14.0/tests/test_metadata.py +163 -0
- dhole_mcp-14.0/tests/test_new_features.py +176 -0
- dhole_mcp-14.0/tests/test_parse.py +149 -0
- dhole_mcp-14.0/tests/test_proxy.py +454 -0
- dhole_mcp-14.0/tests/test_resolve.py +57 -0
- dhole_mcp-14.0/tests/test_search.py +676 -0
- dhole_mcp-14.0/tests/test_security.py +371 -0
- dhole_mcp-14.0/tests/test_server.py +1065 -0
- dhole_mcp-14.0/tests/test_stealth.py +160 -0
- dhole_mcp-14.0/tests/test_structured.py +199 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Default to LF in the repo; git converts to the platform native on checkout.
|
|
2
|
+
* text=auto eol=lf
|
|
3
|
+
|
|
4
|
+
# These must stay CRLF on Windows (pip/console launchers parse them).
|
|
5
|
+
*.bat text eol=crlf
|
|
6
|
+
*.cmd text eol=crlf
|
|
7
|
+
|
|
8
|
+
# Binary assets — never normalize.
|
|
9
|
+
*.png binary
|
|
10
|
+
*.jpg binary
|
|
11
|
+
*.jpeg binary
|
|
12
|
+
*.gif binary
|
|
13
|
+
*.webp binary
|
|
14
|
+
*.ico binary
|
|
15
|
+
*.svg text
|
|
16
|
+
*.pdf binary
|
|
17
|
+
|
|
18
|
+
# Vendored / generated code shouldn't skew the language bar.
|
|
19
|
+
NOTICE.ddgs.txt linguist-vendored
|
|
20
|
+
src/dhole_mcp/search_metasearch.py linguist-vendored=true
|
|
21
|
+
docs/** linguist-generated=true
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: lint-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
ruff:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
|
|
24
|
+
- name: Install ruff
|
|
25
|
+
run: python -m pip install --upgrade pip "ruff>=0.6"
|
|
26
|
+
|
|
27
|
+
- name: Ruff check
|
|
28
|
+
# 规则集与豁免项在 pyproject.toml 的 [tool.ruff] 下配置
|
|
29
|
+
run: ruff check src tests
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: tests-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
os: [ubuntu-latest]
|
|
19
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
20
|
+
include:
|
|
21
|
+
# Windows 只跑 3.11:历史上 3.12/3.13 的 Windows job 会挂起
|
|
22
|
+
# (见 git log "ci: drop Windows 3.12/3.13"),覆盖率由 Ubuntu 保证。
|
|
23
|
+
- os: windows-latest
|
|
24
|
+
python-version: "3.11"
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
|
|
34
|
+
- name: Install
|
|
35
|
+
run: |
|
|
36
|
+
python -m pip install --upgrade pip
|
|
37
|
+
python -m pip install -e ".[dev]"
|
|
38
|
+
|
|
39
|
+
- name: Run tests
|
|
40
|
+
# e2e 测试会真实拉起 dhole.exe 子进程,默认跳过(见 pyproject 的 addopts)
|
|
41
|
+
run: python -m pytest -q
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environment
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# IDE
|
|
16
|
+
.vscode/
|
|
17
|
+
.idea/
|
|
18
|
+
*.swp
|
|
19
|
+
*.swo
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
|
24
|
+
|
|
25
|
+
# Environment/Secrets
|
|
26
|
+
.env
|
|
27
|
+
.env.*
|
|
28
|
+
*.pem
|
|
29
|
+
*.key
|
|
30
|
+
credentials.json
|
|
31
|
+
|
|
32
|
+
# Project caches (runtime)
|
|
33
|
+
.dhole_mcp_cache/
|
|
34
|
+
.dhole_cache/
|
|
35
|
+
*.log
|
|
36
|
+
|
|
37
|
+
# Test artifacts
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
htmlcov/
|
|
40
|
+
.coverage
|
|
41
|
+
coverage.xml
|
|
42
|
+
|
|
43
|
+
# --- personal / agent scratch (never commit) ---
|
|
44
|
+
# These are local-only planning, status, and AI-agent working files. They are
|
|
45
|
+
# not for the public audience and stay out of the repo.
|
|
46
|
+
STATUS.md
|
|
47
|
+
PROJECT.md
|
|
48
|
+
PLAN.md
|
|
49
|
+
TODO.md
|
|
50
|
+
ROADMAP-personal.md
|
|
51
|
+
research/
|
|
52
|
+
notes/
|
|
53
|
+
scratch/
|
|
54
|
+
.pi/
|
|
55
|
+
.claude/
|
|
56
|
+
.cursor/
|
|
57
|
+
keys.md
|
|
58
|
+
dhole-mcp-bug-report.md
|
|
59
|
+
|
|
60
|
+
# Local-only temp helper scripts (one-off fixes, release-note drafts).
|
|
61
|
+
/.fix_*.py
|
|
62
|
+
/.v7*.py
|
|
63
|
+
/.serl*.py
|
|
64
|
+
/.engine_surgery.py
|
|
65
|
+
/.drop_*.py
|
|
66
|
+
/.soften_*.py
|
|
67
|
+
/.release_notes*.md
|
|
68
|
+
V7-SEARCH-PLAN.md
|
|
69
|
+
V10_PLAN.md
|
|
70
|
+
docs/dhole-demo.mp4
|
|
71
|
+
pi-extension/.npmrc
|
|
72
|
+
node_modules/
|
|
73
|
+
package-lock.json
|
|
74
|
+
|
|
75
|
+
# Lint cache (ruff writes .ruff_cache/.gitignore, this is belt-and-braces)
|
|
76
|
+
.ruff_cache/
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
本文件在 11.1.8 版重新建立。更早的发布**未**在此回溯补记——历史请查看
|
|
4
|
+
`git log` 与 GitHub releases 页面。
|
|
5
|
+
|
|
6
|
+
格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
|
|
7
|
+
|
|
8
|
+
> **版本说明。** 自 13.14 起本仓库为个人衍生作品,不再跟随上游项目的发布,
|
|
9
|
+
> 版本号是自己的,与上游版本不可比。`src/dhole_mcp/__init__.py` 中的
|
|
10
|
+
> `__version__` 是版本的唯一权威来源。
|
|
11
|
+
|
|
12
|
+
## [14.0] - 2026-09-20
|
|
13
|
+
|
|
14
|
+
### 重大变更(破坏性)
|
|
15
|
+
- **项目更名:`hound-mcp` → `dhole-mcp`。** 原名与 PyPI 上的其他项目撞名,
|
|
16
|
+
自更新也因此长期关闭。改名覆盖:
|
|
17
|
+
- Python 包目录 `src/hound_mcp/` → `src/dhole_mcp/`,CLI 命令 `hound` → `dhole`
|
|
18
|
+
- 环境变量前缀 `HOUND_*` → `DHOLE_*`(全部读取点)
|
|
19
|
+
- 数据目录 `~/.hound` → `~/.dhole`,缓存 `~/.hound_mcp_cache` →
|
|
20
|
+
`~/.dhole_mcp_cache`(旧缓存不迁移,TTL 到期自然重建)
|
|
21
|
+
- 日志名 `hound-mcp.*` → `dhole-mcp.*`,仓库地址同步更名
|
|
22
|
+
(`github.com/ouli-1242/dhole-mcp`;GitHub 仓库本身需在 Settings 里改同名)
|
|
23
|
+
- **自更新模块注释按新语义改写。** `_DIST_NAME` 指向本仓库自己的分发名;
|
|
24
|
+
`dhole-mcp` 发布到 PyPI 后用 `DHOLE_UPDATE_PACKAGE` 打开自更新。
|
|
25
|
+
|
|
26
|
+
### 兼容措施(迁移期零破坏)
|
|
27
|
+
- 旧环境变量 `HOUND_*` 在包导入时自动迁移为 `DHOLE_*`(同名 `DHOLE_*`
|
|
28
|
+
优先),既有 client 配置里的 env 无需改动。
|
|
29
|
+
- 保留 `hound` CLI 命令别名与 `hound_mcp` 兼容模块(导入时发
|
|
30
|
+
DeprecationWarning),`python -m hound_mcp` 等旧启动方式继续可用;
|
|
31
|
+
确认全部 client 配置已迁移后可删除这两处兼容层。
|
|
32
|
+
|
|
33
|
+
## [13.16] - 2026-09-20
|
|
34
|
+
|
|
35
|
+
### 修复
|
|
36
|
+
- **`links.py` 的 `_norm_host` 会损毁 w 开头的域名。** 它用 `lstrip("www.")` 剥离
|
|
37
|
+
`www.` 前缀,但 `lstrip` 是按字符集剥离而非剥字符串:`wikipedia.org` 变成
|
|
38
|
+
`ikipedia.org`、`web.example.com` 变成 `eb.example.com`、`www.wikipedia.org`
|
|
39
|
+
变成 `ikipedia.org`。这类域名的所有比较与 `.{domain}` 后缀匹配(外链分类、
|
|
40
|
+
primary source 识别)因此全部失效。现改为 `startswith("www.")` + 切片,
|
|
41
|
+
userinfo/端口交给 `urlparse().hostname` 处理。
|
|
42
|
+
- **`fetcher.py` 的 `ElementWrapper.text_content()` 只返回元素自身首段文本**
|
|
43
|
+
(`.text`),嵌套子元素的文字被静默丢弃。现改为拼接 `itertext()`,与 lxml
|
|
44
|
+
原生 `.text_content()` 语义一致。(既有的 3 个测试恰好都用叶子元素,两种
|
|
45
|
+
实现都能通过——现已补嵌套内容回归测试钉住正确行为。)
|
|
46
|
+
- **`smart_search(fetch_content=true)` 静默吞掉单页抓取错误。** 某条 top 结果
|
|
47
|
+
抓取失败时直接从 `fetched_pages` 中消失。现在失败也占位,带
|
|
48
|
+
`content_ok=false` 与脱敏后的 `error` 字段,Agent 能看到缺口及其原因。
|
|
49
|
+
|
|
50
|
+
### 变更
|
|
51
|
+
- **清除"刻舟求剑"式测试(-13 +1)。** 删除把当前实现快照进断言的变更
|
|
52
|
+
探测器:4 个签名/属性存在性测试(`inspect.signature` 查参数、
|
|
53
|
+
`hasattr` 查字段——只在改名时报假警,抓不到行为 bug)、2 个搜索引擎池
|
|
54
|
+
数量/成员冻结(`DEFAULT_ENGINES` 是数据不是逻辑,增删引擎属正常演进)、
|
|
55
|
+
6 个隐身层冻结断言(内存调优参数、单个 stealth flag 字符串、指纹档案
|
|
56
|
+
数量、语言/设备内存枚举)。保留并改造了真契约:关系型不变量(自有参数
|
|
57
|
+
永不含有害自动化参数)、档案自洽(MacIntel⇒Apple GPU、GPU 厂商多样性、
|
|
58
|
+
plugins 非空)、跨引擎共识正确性(DDG/Yahoo 必须同属 Bing 索引家族)。
|
|
59
|
+
test_cli 的 `hasattr` 结构检查替换为真行为测试:子进程中用 import hook
|
|
60
|
+
屏蔽全部重依赖后导入 `dhole_mcp.cli` 必须成功——这才是"坏安装下自愈
|
|
61
|
+
入口仍可用"的测法。
|
|
62
|
+
- **`get()`/`bulk_get()` 的 `auth` / `proxy_auth` 现在真正生效**(闭合 13.x
|
|
63
|
+
记录的已知缺口):`auth` 转成 Basic `Authorization` 头(绝不覆盖调用方
|
|
64
|
+
显式设置的头);`proxy_auth` 经 URL 编码后嵌入代理地址交给 HTTP 层;显式
|
|
65
|
+
`proxy_auth` 优先于代理串里已嵌入的凭据。dict 代理
|
|
66
|
+
(`{server, username, password}`)现在也能到达 HTTP 层——此前所有
|
|
67
|
+
`proxy if isinstance(proxy, str) else None` 写法都把它静默忽略,
|
|
68
|
+
`smart_fetch` 带 dict 代理时只有浏览器层走代理、HTTP 层直连。
|
|
69
|
+
- **依赖刷新,约束两端均验证通过:** mcp 2.0 → 2.2、pydantic 2.13.5、
|
|
70
|
+
trafilatura 2.2、httpx 0.27 → 0.28(dhole 早已使用新的 `proxy=` API)、
|
|
71
|
+
anyio/starlette/uvicorn/lxml/beautifulsoup4/cssselect/h2/markdownify 升至
|
|
72
|
+
最新,primp 1.3 → **2.0**(主版本)。primp 2.0 移除了响应的 `.reason`
|
|
73
|
+
属性——`fetcher.py` 已有 `hasattr` 守护;完整测试套件 + 真实冒烟(构造
|
|
74
|
+
参数、`headers_update`、`follow_redirects=False` 逐跳重定向解析、cookies)
|
|
75
|
+
在 1.3.1 与 2.0.1 上均通过。patchright/playwright 保持 1.61 以匹配本地
|
|
76
|
+
已安装的浏览器。
|
|
77
|
+
- **工具可发现性重写(为什么模型此前很少主动使用 dhole)。** 模型选工具
|
|
78
|
+
取决于描述的第一句话,而 dhole 的描述原本是功能清单。8 个工具全部改为:
|
|
79
|
+
先说任务触发场景 + 显式声明"用本工具而非内置 WebFetch / web search" +
|
|
80
|
+
理由(反爬绕过、JS 渲染、PDF/OCR、无密钥引擎),功能/信号细节后置。
|
|
81
|
+
connect-time `instructions` 同样重写:祈使句路由表,"prefer dhole over
|
|
82
|
+
built-ins" 放首行。顺带修正两处过时描述:默认引擎池补上 `bing`(代码
|
|
83
|
+
默认早已包含它),删除已失效的 "'bing' maps to yahoo" 映射说明。
|
|
84
|
+
- README 环境变量表现在列出代码实际读取的全部变量
|
|
85
|
+
(`DHOLE_SEARCH_DEADLINE`、`DHOLE_BRIGHTDATA_*`、`DHOLE_SSRF_DNS_RECHECK`、
|
|
86
|
+
`DHOLE_UPDATE_*`)。
|
|
87
|
+
|
|
88
|
+
### 新增
|
|
89
|
+
- `include_media` 现在也能抓取懒加载图片:当 `src` 为空或 `data:` 占位图时,
|
|
90
|
+
从同一个 `<img>` 标签读取 `data-src`(`src` 是真实 URL 时仍优先)。
|
|
91
|
+
|
|
92
|
+
## [13.14] - 2026-09-10
|
|
93
|
+
|
|
94
|
+
### 修复
|
|
95
|
+
- **日志凭据泄漏。** `fetcher.py` 重试时把原始异常文本写进 `logger.warning`,
|
|
96
|
+
含 `user:pass@` 的代理 URL 会落进日志。现统一走 `security.redact_api_key()`
|
|
97
|
+
(代码库其他地方已在用)。
|
|
98
|
+
- **`RuntimeWarning: coroutine 'ProxyPool.health_check' was never awaited`。**
|
|
99
|
+
`_kick_health_check()` 在调用 `asyncio.create_task()` 之前就求值了
|
|
100
|
+
`pool.health_check()`,没有运行中的事件循环时协程被创建后即丢弃。现在
|
|
101
|
+
先检查事件循环。
|
|
102
|
+
- **过期的类型标注。** `server.py` 两个辅助函数仍标注已不存在的
|
|
103
|
+
`_ScraplingResponse`;`ocr.py` 和 `server.py` 引用 `PdfResult` /
|
|
104
|
+
`CrawlResponseModel` 但未导入。标注是惰性的所以没有崩溃,但类型是错的。
|
|
105
|
+
- 清理死赋值(`browser.py`、`pdf_extractor.py`、`search_engines.py`、
|
|
106
|
+
`server.py`),重命名歧义变量 `l`。
|
|
107
|
+
|
|
108
|
+
### 变更
|
|
109
|
+
- **版本单一来源。** `pyproject.toml` 不再硬编码 `99.0.0`,改经
|
|
110
|
+
`[tool.hatch.version]` 读取 `src/dhole_mcp/__init__.py` 的 `__version__`。
|
|
111
|
+
此前 `pip show` 报 99.0.0、CLI 报 11.1.8、package.json 写 11.1.6,三处
|
|
112
|
+
不一致。
|
|
113
|
+
- 补声明"被导入但未列出"的依赖:`beautifulsoup4`(`search_engines.py`
|
|
114
|
+
导入——缺失时该代码路径静默降级为空结果),以及仅经
|
|
115
|
+
`httpx[http2,socks]` 传递解析的 `h2` 和 `httpcore`。
|
|
116
|
+
- **自更新不再指向上游包。** 本仓库是个人衍生作品,但 `dhole -u` 原先执行
|
|
117
|
+
`pip install dhole-mcp==<latest>`——那是*上游*发行版,会把上游代码装进来
|
|
118
|
+
覆盖本 fork。自更新现默认关闭:`dhole -v` 显示 "self-update off",
|
|
119
|
+
`dhole -u` 拒绝执行(含显式版本号)并指向
|
|
120
|
+
`git pull && python -m pip install -e .`。发布自己的发行版后可用
|
|
121
|
+
`DHOLE_UPDATE_PACKAGE=<发行版名>` 加可选 `DHOLE_UPDATE_INDEX_URL`
|
|
122
|
+
重新启用。生成的修复脚本与 Windows 助手跟随同一发行版名。
|
|
123
|
+
- 自 13.14 起版本号是本 fork 自己的,与上游版本不可比。
|
|
124
|
+
- 移除代码注释与测试分组标题里的 `(upstream v12.0.0)` 式溯源标记。
|
|
125
|
+
- 删除 `package.json` 里悬空的 `pi.extensions` 条目:它指向的
|
|
126
|
+
`pi-extension/extensions/dhole.ts` 不在本仓库中。
|
|
127
|
+
|
|
128
|
+
### 新增
|
|
129
|
+
- `.github/workflows/test.yml` 与 `.github/workflows/lint.yml`——仓库此前
|
|
130
|
+
没有 CI,测试套件和 lint 无强制。
|
|
131
|
+
- `CONTRIBUTING.md` 与本 changelog。
|
|
132
|
+
- `[tool.ruff]` 配置及 `dev` extras 中的 `ruff`。
|
|
133
|
+
|
|
134
|
+
### 已知缺口
|
|
135
|
+
- 抓取工具的 `auth` / `proxy_auth` 只校验不生效:`HTTPSession` / `http_get`
|
|
136
|
+
不接受它们。当时保留原行为;见 `src/dhole_mcp/server.py` 中 `bulk_get`
|
|
137
|
+
的注释。(已于 13.15 修复。)
|
|
138
|
+
- `LICENSE` 与 `NOTICE.ddgs.txt` 保留原始版权声明。停止追踪上游*版本*并
|
|
139
|
+
不改变这一点:除非重写代码,MIT 要求保留这些声明。
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for looking at Dhole. This is a small, opinionated MCP server; the notes
|
|
4
|
+
below are what you need to get a green local run.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/ouli-1242/dhole-mcp.git
|
|
10
|
+
cd dhole-mcp
|
|
11
|
+
python -m pip install -e ".[dev]"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Optional extras (browser engine, PDF, OCR, local file parsing) are in `[all]`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python -m pip install -e ".[dev,all]"
|
|
18
|
+
playwright install chromium # required for the anti-bot browser path
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Without `[all]`, dhole degrades to HTTP-only mode instead of failing — that
|
|
22
|
+
degradation is a supported configuration and is covered by tests.
|
|
23
|
+
|
|
24
|
+
## Tests
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pytest # default run; e2e tests are deselected
|
|
28
|
+
pytest -m e2e # spawns a real dhole.exe subprocess
|
|
29
|
+
pytest tests/test_search.py -q
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Two things worth knowing before you debug a "test doesn't see my change":
|
|
33
|
+
|
|
34
|
+
1. **`pythonpath = ["src"]` in `pyproject.toml` is load-bearing.** The dev venv
|
|
35
|
+
often has a *built* wheel installed rather than an editable install. Without
|
|
36
|
+
that setting, pytest silently imports the stale installed copy and your edits
|
|
37
|
+
to `src/` go untested. This has bitten the project more than once.
|
|
38
|
+
2. `addopts = ["-m", "not e2e"]` deselects the e2e tests by default. They are
|
|
39
|
+
slower and need a built executable.
|
|
40
|
+
|
|
41
|
+
## Lint
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
ruff check src tests # config lives in pyproject.toml [tool.ruff]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
CI runs `pytest` (Ubuntu 3.11–3.14, Windows 3.11) and `ruff check` on every push
|
|
48
|
+
to `master` and every pull request.
|
|
49
|
+
|
|
50
|
+
## Layout
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
src/dhole_mcp/
|
|
54
|
+
server.py MCP tool schemas + dispatch + orchestration
|
|
55
|
+
fetcher.py primp-based HTTP session and Response
|
|
56
|
+
browser.py patchright stealth browser
|
|
57
|
+
search*.py keyless metasearch backends, proxy pool, reranker
|
|
58
|
+
crawl.py, sitemap.py same-domain crawling
|
|
59
|
+
pdf_extractor.py, ocr.py, parse.py, structured.py, metadata.py
|
|
60
|
+
extractor.py, trafilatura_extractor.py HTML -> markdown paths
|
|
61
|
+
security.py URL validation, SSRF checks, credential redaction
|
|
62
|
+
envelope.py, errors.py, cache.py cross-cutting helpers
|
|
63
|
+
tests/ unit + integration tests (no live network by default)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Conventions
|
|
67
|
+
|
|
68
|
+
- Commit messages follow `type(scope): summary` — e.g. `fix(search): ...`,
|
|
69
|
+
`feat(server): ...`, `docs: ...`, `chore: ...`.
|
|
70
|
+
- Never commit credentials. API keys are read from environment variables only;
|
|
71
|
+
see the note in `src/dhole_mcp/search_metasearch.py`. Error text and logs must
|
|
72
|
+
go through `security.redact_api_key()` before being surfaced.
|
|
73
|
+
- Every behaviour change ships with a test that fails without it.
|
|
74
|
+
- Keep tool definitions token-lean — the tool descriptions are part of the
|
|
75
|
+
prompt budget for every agent session.
|
|
76
|
+
|
|
77
|
+
## Reporting bugs
|
|
78
|
+
|
|
79
|
+
Include the `dhole -v` output, the exact tool call, and whether the browser
|
|
80
|
+
extras were installed. `DHOLE_DEBUG=1` (or `-v`) increases log verbosity.
|
dhole_mcp-14.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bishesh Bhandari
|
|
4
|
+
Modifications Copyright (c) 2026 ouli-1242
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
NOTICE — vendored ddgs metasearch code
|
|
2
|
+
|
|
3
|
+
The file `src/dhole_mcp/search_metasearch.py` in this project is derived
|
|
4
|
+
from ddgs (https://github.com/deedy5/ddgs), a metasearch library aggregating
|
|
5
|
+
diverse web search services. ddgs is distributed under the MIT License
|
|
6
|
+
(see below). It has been vendored, stripped to text search only, and adapted
|
|
7
|
+
(async-native parallel aggregation with early-return-on-quorum, dhole-specific
|
|
8
|
+
tuning) for use inside dhole-mcp.
|
|
9
|
+
|
|
10
|
+
Per the MIT terms, the original copyright notice + permission notice are
|
|
11
|
+
reproduced here and the ddgs LICENSE is preserved alongside this notice.
|
|
12
|
+
|
|
13
|
+
ddgs dependencies used transitively at runtime (primp, httpx[http2]/h2,
|
|
14
|
+
fake-useragent, lxml) remain separate upstream packages under their own
|
|
15
|
+
licenses (primp/httpx/fake-useragent/lxml are MIT/BSD-compatible).
|
|
16
|
+
|
|
17
|
+
------------------------------------------------------------------------------
|
|
18
|
+
MIT License
|
|
19
|
+
|
|
20
|
+
Copyright (c) 2022 deedy5
|
|
21
|
+
|
|
22
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
23
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
24
|
+
in the Software without restriction, including without limitation the rights
|
|
25
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
26
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
27
|
+
furnished to do so, subject to the following conditions:
|
|
28
|
+
|
|
29
|
+
The above copyright notice and this permission notice shall be included in all
|
|
30
|
+
copies or substantial portions of the Software.
|
|
31
|
+
|
|
32
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
33
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
34
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
35
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
36
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
37
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
38
|
+
SOFTWARE.
|
dhole_mcp-14.0/PKG-INFO
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dhole-mcp
|
|
3
|
+
Version: 14.0
|
|
4
|
+
Summary: Web research for AI agents. Fetch any page with anti-bot bypass plus web search. $0 forever. No scrapling dependency: dhole uses primp + patchright directly. Graceful degradation when browser deps are unavailable (HTTP-only mode on Termux/Android). Token-optimized tool definitions, polished cross-platform CLI, brick-proof self-update with dhole doctor + rollback.
|
|
5
|
+
Project-URL: Repository, https://github.com/ouli-1242/dhole-mcp
|
|
6
|
+
Project-URL: Issues, https://github.com/ouli-1242/dhole-mcp/issues
|
|
7
|
+
Author-email: ouli-1242 <ouli1242@gmail.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE.ddgs.txt
|
|
10
|
+
Keywords: ai-agent,anti-bot,cloudflare-bypass,content-extraction,mcp,mcp-server,web-scraping
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
24
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
25
|
+
Requires-Dist: cssselect>=1.2
|
|
26
|
+
Requires-Dist: fake-useragent>=2.0
|
|
27
|
+
Requires-Dist: h2>=4.0
|
|
28
|
+
Requires-Dist: httpcore<2.0,>=1.0
|
|
29
|
+
Requires-Dist: httpx[http2,socks]>=0.27
|
|
30
|
+
Requires-Dist: lxml>=5.0
|
|
31
|
+
Requires-Dist: markdownify>=1.2.0
|
|
32
|
+
Requires-Dist: mcp<3,>=2.0
|
|
33
|
+
Requires-Dist: primp>=1.0
|
|
34
|
+
Requires-Dist: pydantic>=2.0
|
|
35
|
+
Requires-Dist: trafilatura>=2.0.0
|
|
36
|
+
Provides-Extra: all
|
|
37
|
+
Requires-Dist: browserforge>=1.2.4; extra == 'all'
|
|
38
|
+
Requires-Dist: onnxruntime>=1.16; extra == 'all'
|
|
39
|
+
Requires-Dist: openpyxl>=3.1; extra == 'all'
|
|
40
|
+
Requires-Dist: patchright>=1.50; extra == 'all'
|
|
41
|
+
Requires-Dist: pdfplumber>=0.11.0; extra == 'all'
|
|
42
|
+
Requires-Dist: playwright>=1.50; extra == 'all'
|
|
43
|
+
Requires-Dist: pypdfium2>=4.30; extra == 'all'
|
|
44
|
+
Requires-Dist: python-docx>=1.0; extra == 'all'
|
|
45
|
+
Requires-Dist: rapidocr>=3.0; extra == 'all'
|
|
46
|
+
Requires-Dist: requests>=2.31; extra == 'all'
|
|
47
|
+
Requires-Dist: tokenizers>=0.20; extra == 'all'
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest-mock>=3.14; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
52
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
53
|
+
Description-Content-Type: text/markdown
|
|
54
|
+
|
|
55
|
+
<div align="center">
|
|
56
|
+
|
|
57
|
+
# Dhole
|
|
58
|
+
|
|
59
|
+
**让 AI 代理访问互联网:抓取 · 爬取 · 搜索,内置反爬,$0 无密钥。**
|
|
60
|
+
|
|
61
|
+
[MIT](LICENSE) · Python 3.11+
|
|
62
|
+
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
Dhole 是一个 [MCP](https://modelcontextprotocol.io) 服务器,为 AI 代理提供网页抓取、整站爬取和无密钥网页搜索。HTTP 被拦截时自动升级到反检测浏览器,可读取 PDF(含扫描件 OCR),全部本地运行、无需任何 API key。
|
|
66
|
+
|
|
67
|
+
## 功能特性
|
|
68
|
+
|
|
69
|
+
- **智能抓取**:HTTP 优先(~1 秒),被拦截或遇到 JS 空壳时自动升级 Patchright 隐身浏览器并求解 Cloudflare 验证;全部失败时回退 Wayback Machine 快照
|
|
70
|
+
- **网页搜索**:多引擎并行(bing / duckduckgo / brave / yahoo / yandex 等),本地 ONNX 神经重排序,跨引擎共识排名;`fetch_content=true` 直接抓回 top3 全文
|
|
71
|
+
- **整站爬取**:同域最佳优先遍历,内容自适应提取,sitemap 模式,页数 / 深度 / token 预算控制
|
|
72
|
+
- **PDF + OCR**:结构化 Markdown 输出,扫描件与 CID 损坏自动 OCR
|
|
73
|
+
- **结构化提取**:CSS 选择器 / JSON-LD / 自动模式 schema,支持多 URL 批量并行
|
|
74
|
+
- **Agent 友好**:每次调用返回 `next_action` 建议与来源分类;超长内容自动分页;SQLite 缓存加速重复抓取
|
|
75
|
+
- **优雅降级**:浏览器依赖缺失时自动切换纯 HTTP 模式(Termux / 精简容器可用)
|
|
76
|
+
|
|
77
|
+
## 安装
|
|
78
|
+
|
|
79
|
+
未发布到 PyPI,从源码安装。需要 Python 3.11+。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/ouli-1242/dhole-mcp.git
|
|
83
|
+
cd dhole-mcp
|
|
84
|
+
pip install .[all] # 完整版;精简版(纯 HTTP + 搜索)用 pip install .
|
|
85
|
+
playwright install chromium # 反检测浏览器引擎(~150MB,完整版需要)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
卸载:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip uninstall dhole-mcp
|
|
92
|
+
rm -rf ~/.dhole # 运行时缓存(Windows: rd /s /q %USERPROFILE%\.dhole)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 使用
|
|
96
|
+
|
|
97
|
+
在 MCP 客户端(Claude Code / Cursor / OpenCode 等)配置中添加:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{ "mcpServers": { "dhole": { "command": "dhole" } } }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
无需参数、无需密钥、无需环境变量。CLI 自带诊断命令:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
dhole -v # 查看版本 + 更新状态
|
|
107
|
+
dhole -u # 自更新(本 fork 默认关闭)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 工具
|
|
111
|
+
|
|
112
|
+
| 工具 | 功能 |
|
|
113
|
+
|------|------|
|
|
114
|
+
| `smart_fetch` | 抓取任意 URL:自动反爬升级、PDF/OCR、批量、聚焦提取、页面交互、结构化提取 |
|
|
115
|
+
| `smart_search` | 无密钥网页搜索:多引擎并行、神经重排序、可同时抓回全文 |
|
|
116
|
+
| `smart_crawl` | 同域最佳优先爬取,支持 sitemap 模式与关键词过滤 |
|
|
117
|
+
| `screenshot` | 页面截图(多模态代理专用) |
|
|
118
|
+
| `parse` | 本地文件解析(.html/.docx/.xlsx/.csv → Markdown) |
|
|
119
|
+
| `feed_fetch` | 批量抓取 RSS/Atom feed 最新条目 |
|
|
120
|
+
| `resolve_url` | 解析 URL 最终地址(跟随重定向,不下载页面体) |
|
|
121
|
+
| `cache_clear` | 清除抓取缓存 |
|
|
122
|
+
|
|
123
|
+
## 配置
|
|
124
|
+
|
|
125
|
+
所有环境变量均可选,默认零配置可用:
|
|
126
|
+
|
|
127
|
+
| 变量 | 用途 |
|
|
128
|
+
|------|------|
|
|
129
|
+
| `DHOLE_SEARCH_PROXY` | 搜索引擎代理,逗号分隔可轮换(也自动读取 `HTTPS_PROXY` 等) |
|
|
130
|
+
| `DHOLE_BROWSER_IDLE_TIMEOUT` | 浏览器空闲关闭秒数(默认 300,`0` 永不关闭) |
|
|
131
|
+
| `DHOLE_SEARCH_DEADLINE` | 单次搜索整体截止秒数(默认 16) |
|
|
132
|
+
| `DHOLE_BRIGHTDATA_API_KEY` | 启用 Bright Data SERP 付费后端(可选) |
|
|
133
|
+
| `DHOLE_SSRF_DNS_RECHECK` | 设 `1` 开启 DNS 解析内网复查(默认关闭) |
|
|
134
|
+
| `DHOLE_UPDATE_PACKAGE` | 自更新目标发行名(发布自己的发行版后设置以启用) |
|
|
135
|
+
|
|
136
|
+
## 已知限制
|
|
137
|
+
|
|
138
|
+
- 无法绕过 DataDome / Akamai / 交互式 Turnstile;需要登录的网站不在设计范围内
|
|
139
|
+
- duckduckgo / brave / yahoo 需要 VPN 可达(bing / yandex 国内直连)
|
|
140
|
+
- 搜索引擎限速时熔断器自动冷却 60 秒,重度使用建议配置代理
|
|
141
|
+
- YouTube 仅能获取少量文本
|
|
142
|
+
|
|
143
|
+
## 贡献
|
|
144
|
+
|
|
145
|
+
欢迎 issue 与 PR,规范见 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
146
|
+
|
|
147
|
+
## 致谢
|
|
148
|
+
|
|
149
|
+
本项目是 [dondai1234/master-fetch](https://github.com/dondai1234/master-fetch) 的二创(衍生作品),上游以 MIT 协议发布,原始版权文本已完整保留于 [LICENSE](LICENSE)。`src/dhole_mcp/search_metasearch.py` 派生自 [ddgs](https://github.com/deedy5/ddgs),声明见 [NOTICE.ddgs.txt](NOTICE.ddgs.txt)。
|
|
150
|
+
|
|
151
|
+
## 许可证
|
|
152
|
+
|
|
153
|
+
[MIT](LICENSE)
|