internal-web-reader 0.2.0__tar.gz → 0.2.2__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.
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/PKG-INFO +5 -6
- internal_web_reader-0.2.2/README.md +25 -0
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/pyproject.toml +1 -1
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader/__main__.py +7 -6
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader/reader.py +67 -36
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/PKG-INFO +5 -6
- internal_web_reader-0.2.0/README.md +0 -26
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/setup.cfg +0 -0
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader/__init__.py +0 -0
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/SOURCES.txt +0 -0
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/dependency_links.txt +0 -0
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/entry_points.txt +0 -0
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/requires.txt +0 -0
- {internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: internal-web-reader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: MCP Server - let Claude Code read internal web pages via browser cookies
|
|
5
5
|
Author: zhanghaoran18006
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,7 +18,7 @@ Requires-Dist: html2text>=2024.2.26
|
|
|
18
18
|
|
|
19
19
|
# Internal Web Reader
|
|
20
20
|
|
|
21
|
-
让 Claude Code
|
|
21
|
+
让 Claude Code 读内网网页。弹个浏览器窗口,登录一次就行。
|
|
22
22
|
|
|
23
23
|
## 安装
|
|
24
24
|
|
|
@@ -34,11 +34,10 @@ claude mcp add -s user internal-web-reader -- internal-web-reader
|
|
|
34
34
|
|
|
35
35
|
## 使用
|
|
36
36
|
|
|
37
|
-
在 Claude Code
|
|
37
|
+
在 Claude Code 里给链接:
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
> 看看 https://
|
|
41
|
-
> 读一下 https://doc.autohome.com.cn/docapi/page/share/share_1Hx8joJJIfo
|
|
40
|
+
> 看看 https://doc.autohome.com.cn/docapi/page/share/share_1Hx8joJJIfo
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
第一次访问需要登录的网站,会弹出 Chrome 窗口。登录一下,之后就不用再登了。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Internal Web Reader
|
|
2
|
+
|
|
3
|
+
让 Claude Code 读内网网页。弹个浏览器窗口,登录一次就行。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install internal-web-reader
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 配置 Claude Code
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
claude mcp add -s user internal-web-reader -- internal-web-reader
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 使用
|
|
18
|
+
|
|
19
|
+
在 Claude Code 里给链接:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
> 看看 https://doc.autohome.com.cn/docapi/page/share/share_1Hx8joJJIfo
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
第一次访问需要登录的网站,会弹出 Chrome 窗口。登录一下,之后就不用再登了。
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "internal-web-reader"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "MCP Server - let Claude Code read internal web pages via browser cookies"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -20,10 +20,11 @@ mcp = FastMCP("internal-web-reader")
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@mcp.tool()
|
|
23
|
-
def
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
def read_web_page(url: str) -> str:
|
|
24
|
+
"""【读网页用这个】通过本地浏览器打开任意 URL 并返回 Markdown 内容。
|
|
25
|
+
专门用于访问公司内网页面(GitLab、Wiki、文档中心等需要 VPN 的网站)。
|
|
26
|
+
也适用于任何公开网页。首次访问需登录的站点会弹出浏览器窗口完成登录,
|
|
27
|
+
之后 session 自动保存。优先使用此工具而非内置 Fetch 来读取网页。
|
|
27
28
|
"""
|
|
28
29
|
page = read_page(url)
|
|
29
30
|
|
|
@@ -36,8 +37,8 @@ def read_page_tool(url: str) -> str:
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
@mcp.tool()
|
|
39
|
-
def
|
|
40
|
-
"""
|
|
40
|
+
def list_web_links(url: str) -> str:
|
|
41
|
+
"""列出网页上的所有链接,用于浏览内网站点结构。"""
|
|
41
42
|
page = read_page(url)
|
|
42
43
|
if not page.links:
|
|
43
44
|
return f"页面 {url} 上未找到链接。"
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
-
import
|
|
5
|
+
import sys
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
-
from pathlib import Path
|
|
8
7
|
from urllib.parse import urljoin
|
|
9
8
|
|
|
10
9
|
from bs4 import BeautifulSoup
|
|
@@ -20,7 +19,6 @@ _H2T.protect_links = True
|
|
|
20
19
|
_H2T.wrap_links = False
|
|
21
20
|
_H2T.unicode_snob = True
|
|
22
21
|
|
|
23
|
-
# 持久化浏览器 profile 目录
|
|
24
22
|
_USER_DATA_DIR = os.path.join(
|
|
25
23
|
os.environ.get("APPDATA", os.path.expanduser("~")),
|
|
26
24
|
"internal-web-reader",
|
|
@@ -36,15 +34,18 @@ class PageResult:
|
|
|
36
34
|
links: list[dict] = field(default_factory=list)
|
|
37
35
|
|
|
38
36
|
|
|
39
|
-
# 全局浏览器实例(进程内复用)
|
|
40
|
-
_browser = None
|
|
41
37
|
_context = None
|
|
42
38
|
_pw = None
|
|
43
39
|
|
|
44
40
|
|
|
41
|
+
def _stderr(msg: str):
|
|
42
|
+
"""写 stderr,保证在 MCP stdio 模式下用户能看到提示。"""
|
|
43
|
+
sys.stderr.write(f"\n{'='*60}\n{msg}\n{'='*60}\n")
|
|
44
|
+
sys.stderr.flush()
|
|
45
|
+
|
|
46
|
+
|
|
45
47
|
def _ensure_browser():
|
|
46
|
-
|
|
47
|
-
global _browser, _context, _pw
|
|
48
|
+
global _context, _pw
|
|
48
49
|
if _context is not None:
|
|
49
50
|
return _context
|
|
50
51
|
|
|
@@ -61,58 +62,89 @@ def _ensure_browser():
|
|
|
61
62
|
locale="zh-CN",
|
|
62
63
|
args=["--disable-blink-features=AutomationControlled"],
|
|
63
64
|
)
|
|
64
|
-
log.info("浏览器已启动")
|
|
65
65
|
return _context
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def _is_login_page(page) -> bool:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if any(k in
|
|
69
|
+
url_lower = page.url.lower()
|
|
70
|
+
login_keywords = ["/login", "/signin", "/sso", "/cas", "/oauth", "/auth", "passport"]
|
|
71
|
+
if any(k in url_lower for k in login_keywords):
|
|
72
72
|
return True
|
|
73
|
-
|
|
74
|
-
pw_inputs = page.query_selector_all('input[type="password"]')
|
|
75
|
-
if pw_inputs and len(pw_inputs) > 0:
|
|
73
|
+
if page.query_selector('input[type="password"]'):
|
|
76
74
|
return True
|
|
77
75
|
return False
|
|
78
76
|
|
|
79
77
|
|
|
78
|
+
def _wait_for_login(page, timeout_ms: int = 300_000):
|
|
79
|
+
"""
|
|
80
|
+
检测到登录页后,在页面上注入提示,并等待用户完成登录。
|
|
81
|
+
"""
|
|
82
|
+
_stderr(
|
|
83
|
+
" 需要登录!请切换到弹出的浏览器窗口完成登录。\n"
|
|
84
|
+
" 登录成功后会自动继续,不用回来操作终端。"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# 在页面上注入一个醒目的提示
|
|
88
|
+
page.evaluate("""() => {
|
|
89
|
+
const banner = document.createElement('div');
|
|
90
|
+
banner.id = '__iwr_login_hint__';
|
|
91
|
+
banner.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:999999;'
|
|
92
|
+
+ 'background:#ff6b35;color:white;padding:16px;text-align:center;'
|
|
93
|
+
+ 'font-size:18px;font-weight:bold;font-family:sans-serif;'
|
|
94
|
+
+ 'box-shadow:0 4px 12px rgba(0,0,0,0.3);';
|
|
95
|
+
banner.textContent = 'Internal Web Reader: Please log in here, then wait a few seconds...';
|
|
96
|
+
document.body.prepend(banner);
|
|
97
|
+
}""")
|
|
98
|
+
|
|
99
|
+
# 等待:密码框消失 + URL 不再包含 login 关键词
|
|
100
|
+
import time
|
|
101
|
+
start = time.time()
|
|
102
|
+
while (time.time() - start) * 1000 < timeout_ms:
|
|
103
|
+
time.sleep(2)
|
|
104
|
+
current_url = page.url.lower()
|
|
105
|
+
has_pw = page.query_selector('input[type="password"]')
|
|
106
|
+
|
|
107
|
+
# 如果 URL 不再含 login 关键词 且 没有密码框 → 登录成功
|
|
108
|
+
login_in_url = any(k in current_url for k in ["/login", "/signin", "/sso", "/cas", "/oauth", "passport"])
|
|
109
|
+
if not login_in_url and not has_pw:
|
|
110
|
+
# 移除提示
|
|
111
|
+
page.evaluate("""() => {
|
|
112
|
+
const el = document.getElementById('__iwr_login_hint__');
|
|
113
|
+
if (el) el.remove();
|
|
114
|
+
}""")
|
|
115
|
+
_stderr(" 登录成功!正在读取页面...")
|
|
116
|
+
page.wait_for_load_state("domcontentloaded", timeout=30_000)
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
_stderr(" 登录等待超时(5分钟),尝试继续读取当前页面...")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
|
|
80
123
|
def read_page(url: str, timeout: float = 60) -> PageResult:
|
|
81
|
-
"""用浏览器打开 URL,返回清洗后的 Markdown。"""
|
|
82
124
|
ctx = _ensure_browser()
|
|
83
|
-
|
|
84
|
-
# 用现有 page 或新建
|
|
85
125
|
page = ctx.pages[0] if ctx.pages else ctx.new_page()
|
|
86
126
|
|
|
127
|
+
# 让浏览器窗口获得焦点
|
|
128
|
+
try:
|
|
129
|
+
page.bring_to_front()
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
|
|
87
133
|
page.goto(url, wait_until="domcontentloaded", timeout=timeout * 1000)
|
|
88
134
|
|
|
89
|
-
#
|
|
135
|
+
# 登录检测
|
|
90
136
|
if _is_login_page(page):
|
|
91
|
-
|
|
92
|
-
# 等 URL 变化(登录成功后会跳转)或者最多等 120 秒
|
|
93
|
-
try:
|
|
94
|
-
page.wait_for_url(
|
|
95
|
-
lambda u: u != page.url and "login" not in u.lower(),
|
|
96
|
-
timeout=120_000,
|
|
97
|
-
)
|
|
98
|
-
# 等页面加载完
|
|
99
|
-
page.wait_for_load_state("domcontentloaded", timeout=30_000)
|
|
100
|
-
log.info(f"登录成功,当前页面: {page.url}")
|
|
101
|
-
except Exception:
|
|
102
|
-
log.warning("等待登录超时,尝试继续...")
|
|
137
|
+
_wait_for_login(page)
|
|
103
138
|
|
|
104
|
-
# 获取页面内容
|
|
105
139
|
final_url = page.url
|
|
106
140
|
html = page.content()
|
|
107
141
|
title = page.title() or url
|
|
108
142
|
|
|
109
143
|
soup = BeautifulSoup(html, "html.parser")
|
|
110
144
|
|
|
111
|
-
# 去噪
|
|
112
145
|
for tag in soup.find_all(["script", "style", "noscript", "iframe", "svg", "nav", "footer", "aside"]):
|
|
113
146
|
tag.decompose()
|
|
114
147
|
|
|
115
|
-
# 找主体
|
|
116
148
|
main = None
|
|
117
149
|
for sel in ("article", "main", '[role="main"]', ".content", ".article-content", ".markdown-body", "#content", "#main"):
|
|
118
150
|
el = soup.select_one(sel)
|
|
@@ -125,14 +157,13 @@ def read_page(url: str, timeout: float = 60) -> PageResult:
|
|
|
125
157
|
markdown = _H2T.handle(str(target)).strip()
|
|
126
158
|
|
|
127
159
|
if len(markdown) > 200_000:
|
|
128
|
-
markdown = markdown[:200_000] + "\n\n... [
|
|
160
|
+
markdown = markdown[:200_000] + "\n\n... [truncated]"
|
|
129
161
|
|
|
130
162
|
return PageResult(url=final_url, title=title, content=markdown, links=links)
|
|
131
163
|
|
|
132
164
|
|
|
133
165
|
def close_browser():
|
|
134
|
-
|
|
135
|
-
global _browser, _context, _pw
|
|
166
|
+
global _context, _pw
|
|
136
167
|
if _context:
|
|
137
168
|
try:
|
|
138
169
|
_context.close()
|
{internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: internal-web-reader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: MCP Server - let Claude Code read internal web pages via browser cookies
|
|
5
5
|
Author: zhanghaoran18006
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,7 +18,7 @@ Requires-Dist: html2text>=2024.2.26
|
|
|
18
18
|
|
|
19
19
|
# Internal Web Reader
|
|
20
20
|
|
|
21
|
-
让 Claude Code
|
|
21
|
+
让 Claude Code 读内网网页。弹个浏览器窗口,登录一次就行。
|
|
22
22
|
|
|
23
23
|
## 安装
|
|
24
24
|
|
|
@@ -34,11 +34,10 @@ claude mcp add -s user internal-web-reader -- internal-web-reader
|
|
|
34
34
|
|
|
35
35
|
## 使用
|
|
36
36
|
|
|
37
|
-
在 Claude Code
|
|
37
|
+
在 Claude Code 里给链接:
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
> 看看 https://
|
|
41
|
-
> 读一下 https://doc.autohome.com.cn/docapi/page/share/share_1Hx8joJJIfo
|
|
40
|
+
> 看看 https://doc.autohome.com.cn/docapi/page/share/share_1Hx8joJJIfo
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
第一次访问需要登录的网站,会弹出 Chrome 窗口。登录一下,之后就不用再登了。
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Internal Web Reader
|
|
2
|
-
|
|
3
|
-
让 Claude Code 能读内网网页。
|
|
4
|
-
|
|
5
|
-
## 安装
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
pip install internal-web-reader
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## 配置 Claude Code
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
claude mcp add -s user internal-web-reader -- internal-web-reader
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 使用
|
|
18
|
-
|
|
19
|
-
在 Claude Code 里直接给链接:
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
> 看看 https://git.corpautohome.com/xxx/-/issues/123
|
|
23
|
-
> 读一下 https://doc.autohome.com.cn/docapi/page/share/share_1Hx8joJJIfo
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Cookie 自动从浏览器提取。前提:浏览器里登录过、VPN 连着。
|
|
File without changes
|
|
File without changes
|
{internal_web_reader-0.2.0 → internal_web_reader-0.2.2}/src/internal_web_reader.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|