cnks 0.2.5__py3-none-any.whl → 0.3.1__py3-none-any.whl
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.
- cnks-0.3.1.dist-info/METADATA +101 -0
- cnks-0.3.1.dist-info/RECORD +17 -0
- cnks-0.3.1.dist-info/entry_points.txt +5 -0
- src/ThisIsAServerSample.py +377 -0
- src/__init__.py +7 -0
- src/cache.py +451 -0
- src/citzer.py +868 -0
- src/click50.py +527 -0
- src/client.py +135 -0
- src/cssci.py +267 -0
- src/extractlink.py +262 -0
- src/ifverify.py +134 -0
- src/main.py +70 -0
- src/searcher.py +767 -0
- src/server.py +487 -0
- src/worker.py +219 -0
- cnks/__init__.py +0 -50
- cnks/server.py +0 -1876
- cnks-0.2.5.dist-info/METADATA +0 -181
- cnks-0.2.5.dist-info/RECORD +0 -6
- cnks-0.2.5.dist-info/entry_points.txt +0 -2
- {cnks-0.2.5.dist-info → cnks-0.3.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: cnks
|
3
|
+
Version: 0.3.1
|
4
|
+
Summary: 中国知网搜索与引文处理系统
|
5
|
+
Author-email: bai-z-l <b@iziliang.com>
|
6
|
+
Requires-Python: >=3.12
|
7
|
+
Requires-Dist: mcp[cli]>=1.6.0
|
8
|
+
Requires-Dist: playwright>=1.40.0
|
9
|
+
Requires-Dist: python-dotenv>=1.0.0
|
10
|
+
Description-Content-Type: text/markdown
|
11
|
+
|
12
|
+
# CNKS - 中国知网搜索与引文处理系统
|
13
|
+
|
14
|
+
## 简介
|
15
|
+
|
16
|
+
CNKS是一个用于搜索中国知网并提取引文数据的工具。该系统能够自动化搜索过程,提取文献信息,并以结构化的方式返回结果。
|
17
|
+
|
18
|
+
## 系统架构
|
19
|
+
|
20
|
+
CNKS采用服务器-客户端架构,包含以下主要组件:
|
21
|
+
|
22
|
+
1. **服务器 (Server)**:
|
23
|
+
- 处理来自客户端的请求
|
24
|
+
- 按需调用Worker API处理关键词搜索
|
25
|
+
- 管理搜索结果缓存
|
26
|
+
|
27
|
+
2. **工作模块 (Worker)**:
|
28
|
+
- 提供搜索和数据提取API
|
29
|
+
- 使用Playwright自动浏览网页
|
30
|
+
- 解析和提取引文数据
|
31
|
+
- 不再作为独立进程运行,而是由服务器直接调用
|
32
|
+
|
33
|
+
3. **客户端 (Client)**:
|
34
|
+
- 命令行界面,用于发送搜索请求
|
35
|
+
- 接收并显示搜索结果
|
36
|
+
|
37
|
+
4. **引文处理器 (Citzer)**:
|
38
|
+
- 解析和格式化引文数据
|
39
|
+
- 支持多种引文格式
|
40
|
+
|
41
|
+
## 安装
|
42
|
+
|
43
|
+
### 要求
|
44
|
+
|
45
|
+
- Python 3.12 或更高版本
|
46
|
+
- Playwright
|
47
|
+
- MCP
|
48
|
+
|
49
|
+
### 安装步骤
|
50
|
+
|
51
|
+
1. 克隆仓库:
|
52
|
+
```
|
53
|
+
git clone https://github.com/your-username/cnks.git
|
54
|
+
cd cnks
|
55
|
+
```
|
56
|
+
|
57
|
+
2. 安装依赖:
|
58
|
+
```
|
59
|
+
pip install -e .
|
60
|
+
playwright install
|
61
|
+
```
|
62
|
+
|
63
|
+
## 使用方法
|
64
|
+
|
65
|
+
### 启动服务器
|
66
|
+
|
67
|
+
```
|
68
|
+
cnks
|
69
|
+
```
|
70
|
+
或
|
71
|
+
```
|
72
|
+
cnks-server
|
73
|
+
```
|
74
|
+
|
75
|
+
### 使用客户端发送请求
|
76
|
+
|
77
|
+
```
|
78
|
+
cnks-client "搜索关键词"
|
79
|
+
```
|
80
|
+
|
81
|
+
选项:
|
82
|
+
- `--timeout SECONDS`: 设置响应超时时间(默认为60秒)
|
83
|
+
|
84
|
+
### 直接测试Worker模块 (仅用于调试)
|
85
|
+
|
86
|
+
```
|
87
|
+
cnks-worker-test "搜索关键词"
|
88
|
+
```
|
89
|
+
|
90
|
+
## 配置
|
91
|
+
|
92
|
+
系统可通过以下环境变量进行配置:
|
93
|
+
|
94
|
+
- `CACHE_FILE`: 缓存文件路径,默认为 "cache.json"
|
95
|
+
- `SEARCH_URL`: 搜索URL,默认为中国知网搜索页面
|
96
|
+
|
97
|
+
可以创建`.env`文件设置这些环境变量。
|
98
|
+
|
99
|
+
## 许可证
|
100
|
+
|
101
|
+
[项目许可证信息]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
src/ThisIsAServerSample.py,sha256=p6yhJXMNkFfx1jmeXXXN8UiZ7-h6ZCqx1SqOh1uC5hg,15582
|
2
|
+
src/__init__.py,sha256=XSAvnn1ewEm0AssKtCYuKTuT4-LquKU_pBudXzl0v3Q,179
|
3
|
+
src/cache.py,sha256=KH9W7vajNsgv_IQn6qOTfDR2w9QrxE3YzR7WO5fVzFU,16270
|
4
|
+
src/citzer.py,sha256=9lNmH9pxdxwgG3dZkOVV8jdBzvz2PBS7XCI2Axf8lAg,36548
|
5
|
+
src/click50.py,sha256=C3yc4j2a1K5Qf_9-YpJ8qIeyx4sCuSN4rEkgncIOJJ4,22628
|
6
|
+
src/client.py,sha256=VlNe-0xv9AqVkqPiFilZxtxC-7zv8UcV4pSGVF6ZFpw,4623
|
7
|
+
src/cssci.py,sha256=XDUsbtiDGk2DudbpBl2mP4Z284hKq0YdTBkye1dmdGk,9502
|
8
|
+
src/extractlink.py,sha256=qStR6Zo98RpLAmqQxh788rMAW7JN6dKYeSXQLGYC59U,9896
|
9
|
+
src/ifverify.py,sha256=SFzcy7G4BRzm8takFBj5LLAl_Py5XauQkIorNey-2cw,4332
|
10
|
+
src/main.py,sha256=2zA7LPsC64Ryt2L5q2Gizi7LbzCf3EGQNXU7FobTj5c,1968
|
11
|
+
src/searcher.py,sha256=Sa_T4rGgh1chiceSRtY1a7fwi9jGvh2ot2YL1wX8Hes,31566
|
12
|
+
src/server.py,sha256=dgULoTjFJLcQc65_H0p55-GiKS6TtTvZUS3zAVKfFt0,17308
|
13
|
+
src/worker.py,sha256=O0OwvLKo8QVbvckoiPZXwBdk4dpVel6ePNu02lNbETQ,7549
|
14
|
+
cnks-0.3.1.dist-info/METADATA,sha256=SiSrjfscSgQfgHST2qPlkvhyRWh8tBulgGLpBr45pho,2041
|
15
|
+
cnks-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
16
|
+
cnks-0.3.1.dist-info/entry_points.txt,sha256=ZUeTuZjWMR6j-A-sL2S09SIsc-gFlq6_zl9cvdkypmU,134
|
17
|
+
cnks-0.3.1.dist-info/RECORD,,
|
@@ -0,0 +1,377 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
from mcp.server.models import InitializationOptions
|
4
|
+
import mcp.types as types
|
5
|
+
from mcp.server import NotificationOptions, Server
|
6
|
+
from pydantic import AnyUrl
|
7
|
+
import mcp.server.stdio
|
8
|
+
|
9
|
+
server = Server("playwright-server")
|
10
|
+
|
11
|
+
@server.list_resources()
|
12
|
+
async def handle_list_resources() -> list[types.Resource]:
|
13
|
+
"""
|
14
|
+
List available note resources.
|
15
|
+
Each note is exposed as a resource with a custom note:// URI scheme.
|
16
|
+
"""
|
17
|
+
return []
|
18
|
+
|
19
|
+
@server.read_resource()
|
20
|
+
async def handle_read_resource(uri: AnyUrl) -> str:
|
21
|
+
"""
|
22
|
+
Read a specific note's content by its URI.
|
23
|
+
The note name is extracted from the URI host component.
|
24
|
+
"""
|
25
|
+
raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
|
26
|
+
|
27
|
+
|
28
|
+
@server.list_prompts()
|
29
|
+
async def handle_list_prompts() -> list[types.Prompt]:
|
30
|
+
"""
|
31
|
+
List available prompts.
|
32
|
+
Each prompt can have optional arguments to customize its behavior.
|
33
|
+
"""
|
34
|
+
return []
|
35
|
+
|
36
|
+
@server.get_prompt()
|
37
|
+
async def handle_get_prompt(
|
38
|
+
name: str, arguments: dict[str, str] | None
|
39
|
+
) -> types.GetPromptResult:
|
40
|
+
"""
|
41
|
+
Generate a prompt by combining arguments with server state.
|
42
|
+
The prompt includes all current notes and can be customized via arguments.
|
43
|
+
"""
|
44
|
+
raise ValueError(f"Unknown prompt: {name}")
|
45
|
+
|
46
|
+
|
47
|
+
@server.list_tools()
|
48
|
+
async def handle_list_tools() -> list[types.Tool]:
|
49
|
+
"""
|
50
|
+
List available tools.
|
51
|
+
Each tool specifies its arguments using JSON Schema validation.
|
52
|
+
"""
|
53
|
+
return [
|
54
|
+
# types.Tool(
|
55
|
+
# name="playwright_new_session",
|
56
|
+
# description="Create a new browser session",
|
57
|
+
# inputSchema={
|
58
|
+
# "type": "object",
|
59
|
+
# "properties": {
|
60
|
+
# "url": {"type": "string", "description": "Initial URL to navigate to"}
|
61
|
+
# }
|
62
|
+
# }
|
63
|
+
# ),
|
64
|
+
types.Tool(
|
65
|
+
name="playwright_navigate",
|
66
|
+
description="Navigate to a URL,thip op will auto create a session",
|
67
|
+
inputSchema={
|
68
|
+
"type": "object",
|
69
|
+
"properties": {
|
70
|
+
"url": {"type": "string"}
|
71
|
+
},
|
72
|
+
"required": ["url"]
|
73
|
+
}
|
74
|
+
),
|
75
|
+
types.Tool(
|
76
|
+
name="playwright_screenshot",
|
77
|
+
description="Take a screenshot of the current page or a specific element",
|
78
|
+
inputSchema={
|
79
|
+
"type": "object",
|
80
|
+
"properties": {
|
81
|
+
"name": {"type": "string"},
|
82
|
+
"selector": {"type": "string", "description": "CSS selector for element to screenshot,null is full page"},
|
83
|
+
},
|
84
|
+
"required": ["name"]
|
85
|
+
}
|
86
|
+
),
|
87
|
+
types.Tool(
|
88
|
+
name="playwright_click",
|
89
|
+
description="Click an element on the page using CSS selector",
|
90
|
+
inputSchema={
|
91
|
+
"type": "object",
|
92
|
+
"properties": {
|
93
|
+
"selector": {"type": "string", "description": "CSS selector for element to click"}
|
94
|
+
},
|
95
|
+
"required": ["selector"]
|
96
|
+
}
|
97
|
+
),
|
98
|
+
types.Tool(
|
99
|
+
name="playwright_fill",
|
100
|
+
description="Fill out an input field",
|
101
|
+
inputSchema={
|
102
|
+
"type": "object",
|
103
|
+
"properties": {
|
104
|
+
"selector": {"type": "string", "description": "CSS selector for input field"},
|
105
|
+
"value": {"type": "string", "description": "Value to fill"}
|
106
|
+
},
|
107
|
+
"required": ["selector", "value"]
|
108
|
+
}
|
109
|
+
),
|
110
|
+
types.Tool(
|
111
|
+
name="playwright_evaluate",
|
112
|
+
description="Execute JavaScript in the browser console",
|
113
|
+
inputSchema={
|
114
|
+
"type": "object",
|
115
|
+
"properties": {
|
116
|
+
"script": {"type": "string", "description": "JavaScript code to execute"}
|
117
|
+
},
|
118
|
+
"required": ["script"]
|
119
|
+
}
|
120
|
+
),
|
121
|
+
types.Tool(
|
122
|
+
name="playwright_click_text",
|
123
|
+
description="Click an element on the page by its text content",
|
124
|
+
inputSchema={
|
125
|
+
"type": "object",
|
126
|
+
"properties": {
|
127
|
+
"text": {"type": "string", "description": "Text content of the element to click"}
|
128
|
+
},
|
129
|
+
"required": ["text"]
|
130
|
+
}
|
131
|
+
),
|
132
|
+
types.Tool(
|
133
|
+
name="playwright_get_text_content",
|
134
|
+
description="Get the text content of all elements",
|
135
|
+
inputSchema={
|
136
|
+
"type": "object",
|
137
|
+
"properties": {
|
138
|
+
},
|
139
|
+
}
|
140
|
+
),
|
141
|
+
types.Tool(
|
142
|
+
name="playwright_get_html_content",
|
143
|
+
description="Get the HTML content of the page",
|
144
|
+
inputSchema={
|
145
|
+
"type": "object",
|
146
|
+
"properties": {
|
147
|
+
"selector": {"type": "string", "description": "CSS selector for the element"}
|
148
|
+
},
|
149
|
+
"required": ["selector"]
|
150
|
+
}
|
151
|
+
)
|
152
|
+
]
|
153
|
+
|
154
|
+
import uuid
|
155
|
+
from playwright.async_api import async_playwright
|
156
|
+
import base64
|
157
|
+
import os
|
158
|
+
|
159
|
+
import asyncio
|
160
|
+
|
161
|
+
def update_page_after_click(func):
|
162
|
+
async def wrapper(self, name: str, arguments: dict | None):
|
163
|
+
if not self._sessions:
|
164
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
165
|
+
session_id = list(self._sessions.keys())[-1]
|
166
|
+
page = self._sessions[session_id]["page"]
|
167
|
+
|
168
|
+
new_page_future = asyncio.ensure_future(page.context.wait_for_event("page", timeout=3000))
|
169
|
+
|
170
|
+
result = await func(self, name, arguments)
|
171
|
+
try:
|
172
|
+
new_page = await new_page_future
|
173
|
+
await new_page.wait_for_load_state()
|
174
|
+
self._sessions[session_id]["page"] = new_page
|
175
|
+
except:
|
176
|
+
pass
|
177
|
+
# if page.url != self._sessions[session_id]["page"].url:
|
178
|
+
# await page.wait_for_load_state()
|
179
|
+
# self._sessions[session_id]["page"] = page
|
180
|
+
|
181
|
+
return result
|
182
|
+
return wrapper
|
183
|
+
|
184
|
+
class ToolHandler:
|
185
|
+
_sessions: dict[str, any] = {}
|
186
|
+
_playwright: any = None
|
187
|
+
|
188
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
189
|
+
raise NotImplementedError
|
190
|
+
|
191
|
+
class NewSessionToolHandler(ToolHandler):
|
192
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
193
|
+
self._playwright = await async_playwright().start()
|
194
|
+
browser = await self._playwright.chromium.launch(headless=False)
|
195
|
+
page = await browser.new_page()
|
196
|
+
session_id = str(uuid.uuid4())
|
197
|
+
self._sessions[session_id] = {"browser": browser, "page": page}
|
198
|
+
url = arguments.get("url")
|
199
|
+
if url:
|
200
|
+
if not url.startswith("http://") and not url.startswith("https://"):
|
201
|
+
url = "https://" + url
|
202
|
+
await page.goto(url)
|
203
|
+
return [types.TextContent(type="text", text="succ")]
|
204
|
+
|
205
|
+
class NavigateToolHandler(ToolHandler):
|
206
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
207
|
+
if not self._sessions:
|
208
|
+
await NewSessionToolHandler().handle("",{})
|
209
|
+
# return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
210
|
+
session_id = list(self._sessions.keys())[-1]
|
211
|
+
page = self._sessions[session_id]["page"]
|
212
|
+
url = arguments.get("url")
|
213
|
+
if not url.startswith("http://") and not url.startswith("https://"):
|
214
|
+
url = "https://" + url
|
215
|
+
await page.goto(url)
|
216
|
+
text_content=await GetTextContentToolHandler().handle("",{})
|
217
|
+
return [types.TextContent(type="text", text=f"Navigated to {url}\npage_text_content[:200]:\n\n{text_content[:200]}")]
|
218
|
+
|
219
|
+
class ScreenshotToolHandler(ToolHandler):
|
220
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
221
|
+
if not self._sessions:
|
222
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
223
|
+
session_id = list(self._sessions.keys())[-1]
|
224
|
+
page = self._sessions[session_id]["page"]
|
225
|
+
name = arguments.get("name")
|
226
|
+
selector = arguments.get("selector")
|
227
|
+
# full_page = arguments.get("fullPage", False)
|
228
|
+
if selector:
|
229
|
+
element = await page.locator(selector)
|
230
|
+
await element.screenshot(path=f"{name}.png")
|
231
|
+
else:
|
232
|
+
await page.screenshot(path=f"{name}.png", full_page=True)
|
233
|
+
with open(f"{name}.png", "rb") as image_file:
|
234
|
+
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
|
235
|
+
os.remove(f"{name}.png")
|
236
|
+
return [types.ImageContent(type="image", data=encoded_string, mimeType="image/png")]
|
237
|
+
|
238
|
+
class ClickToolHandler(ToolHandler):
|
239
|
+
@update_page_after_click
|
240
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
241
|
+
if not self._sessions:
|
242
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
243
|
+
session_id = list(self._sessions.keys())[-1]
|
244
|
+
page = self._sessions[session_id]["page"]
|
245
|
+
selector = arguments.get("selector")
|
246
|
+
await page.locator(selector).click()
|
247
|
+
return [types.TextContent(type="text", text=f"Clicked element with selector {selector}")]
|
248
|
+
|
249
|
+
class FillToolHandler(ToolHandler):
|
250
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
251
|
+
if not self._sessions:
|
252
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
253
|
+
session_id = list(self._sessions.keys())[-1]
|
254
|
+
page = self._sessions[session_id]["page"]
|
255
|
+
selector = arguments.get("selector")
|
256
|
+
value = arguments.get("value")
|
257
|
+
await page.locator(selector).fill(value)
|
258
|
+
return [types.TextContent(type="text", text=f"Filled element with selector {selector} with value {value}")]
|
259
|
+
|
260
|
+
class EvaluateToolHandler(ToolHandler):
|
261
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
262
|
+
if not self._sessions:
|
263
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
264
|
+
session_id = list(self._sessions.keys())[-1]
|
265
|
+
page = self._sessions[session_id]["page"]
|
266
|
+
script = arguments.get("script")
|
267
|
+
result = await page.evaluate(script)
|
268
|
+
return [types.TextContent(type="text", text=f"Evaluated script, result: {result}")]
|
269
|
+
|
270
|
+
class ClickTextToolHandler(ToolHandler):
|
271
|
+
@update_page_after_click
|
272
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
273
|
+
if not self._sessions:
|
274
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
275
|
+
session_id = list(self._sessions.keys())[-1]
|
276
|
+
page = self._sessions[session_id]["page"]
|
277
|
+
text = arguments.get("text")
|
278
|
+
await page.locator(f"text={text}").nth(0).click()
|
279
|
+
return [types.TextContent(type="text", text=f"Clicked element with text {text}")]
|
280
|
+
|
281
|
+
class GetTextContentToolHandler(ToolHandler):
|
282
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
283
|
+
if not self._sessions:
|
284
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
285
|
+
session_id = list(self._sessions.keys())[-1]
|
286
|
+
page = self._sessions[session_id]["page"]
|
287
|
+
# text_contents = await page.locator('body').all_inner_texts()
|
288
|
+
|
289
|
+
|
290
|
+
async def get_unique_texts_js(page):
|
291
|
+
unique_texts = await page.evaluate('''() => {
|
292
|
+
var elements = Array.from(document.querySelectorAll('*')); // 先选择所有元素,再进行过滤
|
293
|
+
var uniqueTexts = new Set();
|
294
|
+
|
295
|
+
for (var element of elements) {
|
296
|
+
if (element.offsetWidth > 0 || element.offsetHeight > 0) { // 判断是否可见
|
297
|
+
var childrenCount = element.querySelectorAll('*').length;
|
298
|
+
if (childrenCount <= 3) {
|
299
|
+
var innerText = element.innerText ? element.innerText.trim() : '';
|
300
|
+
if (innerText && innerText.length <= 1000) {
|
301
|
+
uniqueTexts.add(innerText);
|
302
|
+
}
|
303
|
+
var value = element.getAttribute('value');
|
304
|
+
if (value) {
|
305
|
+
uniqueTexts.add(value);
|
306
|
+
}
|
307
|
+
}
|
308
|
+
}
|
309
|
+
}
|
310
|
+
//console.log( Array.from(uniqueTexts));
|
311
|
+
return Array.from(uniqueTexts);
|
312
|
+
}
|
313
|
+
''')
|
314
|
+
return unique_texts
|
315
|
+
|
316
|
+
# 使用示例
|
317
|
+
text_contents = await get_unique_texts_js(page)
|
318
|
+
|
319
|
+
|
320
|
+
|
321
|
+
return [types.TextContent(type="text", text=f"Text content of all elements: {text_contents}")]
|
322
|
+
|
323
|
+
class GetHtmlContentToolHandler(ToolHandler):
|
324
|
+
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
325
|
+
if not self._sessions:
|
326
|
+
return [types.TextContent(type="text", text="No active session. Please create a new session first.")]
|
327
|
+
session_id = list(self._sessions.keys())[-1]
|
328
|
+
page = self._sessions[session_id]["page"]
|
329
|
+
selector = arguments.get("selector")
|
330
|
+
html_content = await page.locator(selector).inner_html()
|
331
|
+
return [types.TextContent(type="text", text=f"HTML content of element with selector {selector}: {html_content}")]
|
332
|
+
|
333
|
+
|
334
|
+
tool_handlers = {
|
335
|
+
"playwright_navigate": NavigateToolHandler(),
|
336
|
+
"playwright_screenshot": ScreenshotToolHandler(),
|
337
|
+
"playwright_click": ClickToolHandler(),
|
338
|
+
"playwright_fill": FillToolHandler(),
|
339
|
+
"playwright_evaluate": EvaluateToolHandler(),
|
340
|
+
"playwright_click_text": ClickTextToolHandler(),
|
341
|
+
"playwright_get_text_content": GetTextContentToolHandler(),
|
342
|
+
"playwright_get_html_content": GetHtmlContentToolHandler(),
|
343
|
+
"playwright_new_session":NewSessionToolHandler(),
|
344
|
+
}
|
345
|
+
|
346
|
+
|
347
|
+
@server.call_tool()
|
348
|
+
async def handle_call_tool(
|
349
|
+
name: str, arguments: dict | None
|
350
|
+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
351
|
+
"""
|
352
|
+
Handle tool execution requests.
|
353
|
+
Tools can modify server state and notify clients of changes.
|
354
|
+
"""
|
355
|
+
if name in tool_handlers:
|
356
|
+
return await tool_handlers[name].handle(name, arguments)
|
357
|
+
else:
|
358
|
+
raise ValueError(f"Unknown tool: {name}")
|
359
|
+
|
360
|
+
async def main():
|
361
|
+
# Run the server using stdin/stdout streams
|
362
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
363
|
+
await server.run(
|
364
|
+
read_stream,
|
365
|
+
write_stream,
|
366
|
+
InitializationOptions(
|
367
|
+
server_name="playwright-plus-server",
|
368
|
+
server_version="0.1.0",
|
369
|
+
capabilities=server.get_capabilities(
|
370
|
+
notification_options=NotificationOptions(),
|
371
|
+
experimental_capabilities={},
|
372
|
+
),
|
373
|
+
),
|
374
|
+
)
|
375
|
+
|
376
|
+
if __name__ == "__main__":
|
377
|
+
asyncio.run(main())
|