kl-mcp-client 2.1.4__tar.gz → 2.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kl-mcp-client
3
- Version: 2.1.4
3
+ Version: 2.1.7
4
4
  Summary: MCP Client for Python
5
5
  Author-email: Kyle <hngan.it@gmail.com>
6
6
  License: MIT
@@ -319,3 +319,35 @@ class MCPTools:
319
319
  "key": key,
320
320
  },
321
321
  )
322
+
323
+ # ======================================================
324
+ # BROWSER RUNTIME (NO SESSION)
325
+ # ======================================================
326
+
327
+ async def create_browser(
328
+ self,
329
+ payload: Optional[Dict[str, Any]] = None,
330
+ ) -> Dict[str, Any]:
331
+ """
332
+ Start browser runtime (idempotent).
333
+ - KHÔNG tạo session
334
+ - Dùng cho warm-up / admin / scheduler
335
+
336
+ payload: ThirdPartyOpenRequest (optional)
337
+ """
338
+ res = await self.client.call_tool(
339
+ "create_browser",
340
+ payload or {},
341
+ )
342
+ return res.get("structuredContent", {})
343
+
344
+ async def release_browser(self) -> Dict[str, Any]:
345
+ """
346
+ Release / stop browser runtime.
347
+ - KHÔNG cần payload
348
+ """
349
+ res = await self.client.call_tool(
350
+ "release_browser",
351
+ {},
352
+ )
353
+ return res.get("structuredContent", {})
@@ -559,3 +559,29 @@ class MCPTools:
559
559
  "scroll",
560
560
  args,
561
561
  ).get("structuredContent", {})
562
+ # ======================================================
563
+ # BROWSER RUNTIME (NO SESSION)
564
+ # ======================================================
565
+ @_ensure_client
566
+ def create_browser(self, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
567
+ """
568
+ Start browser runtime (idempotent).
569
+ Không tạo session, chỉ đảm bảo browser đang RUNNING.
570
+
571
+ payload: map cho ThirdPartyOpenRequest
572
+ """
573
+ return self.client.call_tool(
574
+ "create_browser",
575
+ payload or {},
576
+ ).get("structuredContent", {})
577
+
578
+ @_ensure_client
579
+ def release_browser(self) -> Dict[str, Any]:
580
+ """
581
+ Release / stop browser runtime.
582
+ Không cần payload.
583
+ """
584
+ return self.client.call_tool(
585
+ "release_browser",
586
+ {},
587
+ ).get("structuredContent", {})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kl-mcp-client
3
- Version: 2.1.4
3
+ Version: 2.1.7
4
4
  Summary: MCP Client for Python
5
5
  Author-email: Kyle <hngan.it@gmail.com>
6
6
  License: MIT
@@ -11,4 +11,6 @@ kl_mcp_client.egg-info/requires.txt
11
11
  kl_mcp_client.egg-info/top_level.txt
12
12
  kl_mcp_client/asyncio/__init__.py
13
13
  kl_mcp_client/asyncio/client.py
14
- kl_mcp_client/asyncio/tools.py
14
+ kl_mcp_client/asyncio/tools.py
15
+ tests/test_async_tools.py
16
+ tests/test_tools.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kl-mcp-client"
7
- version = "2.1.4"
7
+ version = "2.1.7"
8
8
  description = "MCP Client for Python"
9
9
  dependencies = [ "requests>=2.32.5", "httpx>=0.28.1",]
10
10
  readme = "README.md"
@@ -0,0 +1,245 @@
1
+ from typing import Any, Dict
2
+
3
+ import pytest
4
+ from kl_mcp_client.asyncio import MCPTools
5
+
6
+ # ======================================================
7
+ # MOCK MCP CLIENT
8
+ # ======================================================
9
+
10
+ class MockMCPClient:
11
+ def __init__(self):
12
+ self.calls = []
13
+
14
+ async def create_session(self, cdpUrl: str):
15
+ self.calls.append(("create_session", cdpUrl))
16
+ return "session-123"
17
+
18
+ async def close_session(self, sessionId: str):
19
+ self.calls.append(("close_session", sessionId))
20
+ return True
21
+
22
+ def list_local_sessions(self):
23
+ return ["session-123", "session-456"]
24
+
25
+ async def call_tool(self, name: str, payload: Dict[str, Any]):
26
+ self.calls.append((name, payload))
27
+
28
+ # ---- fake responses ----
29
+ if name == "screenshot":
30
+ return {
31
+ "content": [
32
+ {
33
+ "type": "image",
34
+ "mimeType": "image/png",
35
+ "data": "BASE64DATA",
36
+ }
37
+ ]
38
+ }
39
+
40
+ if name in ("create_browser", "release_browser"):
41
+ return {
42
+ "structuredContent": {
43
+ "success": True,
44
+ }
45
+ }
46
+
47
+ return {
48
+ "structuredContent": {
49
+ "ok": True,
50
+ "tool": name,
51
+ "payload": payload,
52
+ }
53
+ }
54
+
55
+
56
+ # ======================================================
57
+ # FIXTURES
58
+ # ======================================================
59
+
60
+ @pytest.fixture
61
+ def mock_client():
62
+ return MockMCPClient()
63
+
64
+
65
+ @pytest.fixture
66
+ def tools(mock_client):
67
+ return MCPTools(client=mock_client)
68
+
69
+
70
+ # ======================================================
71
+ # SESSION MANAGEMENT
72
+ # ======================================================
73
+
74
+ @pytest.mark.asyncio
75
+ async def test_create_session(tools):
76
+ res = await tools.create_session("http://localhost:9222")
77
+ assert res["sessionId"] == "session-123"
78
+
79
+
80
+ @pytest.mark.asyncio
81
+ async def test_close_session(tools):
82
+ res = await tools.close_session("session-123")
83
+ assert res["ok"] is True
84
+
85
+
86
+ @pytest.mark.asyncio
87
+ async def test_list_sessions(tools):
88
+ res = await tools.list_sessions()
89
+ assert "sessions" in res
90
+ assert len(res["sessions"]) == 2
91
+
92
+
93
+ # ======================================================
94
+ # BROWSER RUNTIME (NO SESSION)
95
+ # ======================================================
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_create_browser(tools):
99
+ res = await tools.create_browser({"image": "chrome"})
100
+ assert res["success"] is True
101
+
102
+
103
+ @pytest.mark.asyncio
104
+ async def test_release_browser(tools):
105
+ res = await tools.release_browser()
106
+ assert res["success"] is True
107
+
108
+
109
+ # ======================================================
110
+ # NAVIGATION & DOM
111
+ # ======================================================
112
+
113
+ @pytest.mark.asyncio
114
+ async def test_open_page(tools):
115
+ res = await tools.open_page("session-123", "https://example.com")
116
+ assert res["ok"] is True
117
+
118
+
119
+ @pytest.mark.asyncio
120
+ async def test_get_html(tools):
121
+ res = await tools.get_html("session-123")
122
+ assert res["ok"] is True
123
+
124
+
125
+ @pytest.mark.asyncio
126
+ async def test_screenshot(tools):
127
+ res = await tools.screenshot("session-123")
128
+ assert res["type"] == "image"
129
+ assert res["mimeType"] == "image/png"
130
+
131
+
132
+ @pytest.mark.asyncio
133
+ async def test_evaluate(tools):
134
+ res = await tools.evaluate("session-123", "1+1")
135
+ assert res["ok"] is True
136
+
137
+
138
+ # ======================================================
139
+ # ELEMENT UTILITIES
140
+ # ======================================================
141
+
142
+ @pytest.mark.asyncio
143
+ async def test_find_element(tools):
144
+ res = await tools.find_element("session-123", "#main")
145
+ assert res["ok"] is True
146
+
147
+
148
+ @pytest.mark.asyncio
149
+ async def test_find_all(tools):
150
+ res = await tools.find_all("session-123", "div")
151
+ assert res["ok"] is True
152
+
153
+
154
+ @pytest.mark.asyncio
155
+ async def test_get_bounding_box(tools):
156
+ res = await tools.get_bounding_box("session-123", "#main")
157
+ assert res["ok"] is True
158
+
159
+
160
+ @pytest.mark.asyncio
161
+ async def test_click_bounding_box(tools):
162
+ res = await tools.click_bounding_box("session-123", "#btn")
163
+ assert res["ok"] is True
164
+
165
+
166
+ # ======================================================
167
+ # TAB MANAGEMENT
168
+ # ======================================================
169
+
170
+ @pytest.mark.asyncio
171
+ async def test_new_tab(tools):
172
+ res = await tools.new_tab("session-123", "https://example.com")
173
+ assert res["ok"] is True
174
+
175
+
176
+ @pytest.mark.asyncio
177
+ async def test_switch_tab(tools):
178
+ res = await tools.switch_tab("session-123", "tab-1")
179
+ assert res["ok"] is True
180
+
181
+
182
+ # ======================================================
183
+ # ADVANCED ACTIONS
184
+ # ======================================================
185
+
186
+ @pytest.mark.asyncio
187
+ async def test_click_to_text(tools):
188
+ res = await tools.click_to_text("session-123", "Login")
189
+ assert res["ok"] is True
190
+
191
+
192
+ @pytest.mark.asyncio
193
+ async def test_find_element_xpath(tools):
194
+ res = await tools.find_element_xpath("session-123", "//div")
195
+ assert res["ok"] is True
196
+
197
+
198
+ @pytest.mark.asyncio
199
+ async def test_find_element_by_text(tools):
200
+ res = await tools.find_element_by_text("session-123", "Submit")
201
+ assert res["ok"] is True
202
+
203
+
204
+ @pytest.mark.asyncio
205
+ async def test_click_by_node_id(tools):
206
+ res = await tools.click_by_node_id("session-123", 1001)
207
+ assert res["ok"] is True
208
+
209
+
210
+ @pytest.mark.asyncio
211
+ async def test_import_cookies(tools):
212
+ res = await tools.import_cookies("session-123", {"a": "b"})
213
+ assert res["ok"] is True
214
+
215
+
216
+ # ======================================================
217
+ # PERFORM / MOUSE
218
+ # ======================================================
219
+
220
+ @pytest.mark.asyncio
221
+ async def test_perform_click_xy(tools):
222
+ res = await tools.perform_click_xy("session-123", 10, 20)
223
+ assert res["ok"] is True
224
+
225
+
226
+ @pytest.mark.asyncio
227
+ async def test_perform_drag(tools):
228
+ res = await tools.perform_drag("session-123", 0, 0, 100, 100)
229
+ assert res["ok"] is True
230
+
231
+
232
+ @pytest.mark.asyncio
233
+ async def test_perform_hover(tools):
234
+ res = await tools.perform_hover("session-123", 50, 50)
235
+ assert res["ok"] is True
236
+
237
+
238
+ # ======================================================
239
+ # CLEAN TEXT
240
+ # ======================================================
241
+
242
+ @pytest.mark.asyncio
243
+ async def test_get_clean_text(tools):
244
+ res = await tools.get_clean_text("session-123")
245
+ assert res["ok"] is True
@@ -0,0 +1,206 @@
1
+ from typing import Any, Dict
2
+
3
+ import pytest
4
+ from kl_mcp_client import MCPTools # đổi đúng import path của bạn
5
+
6
+ # ======================================================
7
+ # MOCK MCP CLIENT (SYNC)
8
+ # ======================================================
9
+
10
+ class MockMCPClient:
11
+ def __init__(self):
12
+ self.calls = []
13
+
14
+ def create_session(self, cdpUrl: str):
15
+ self.calls.append(("create_session", cdpUrl))
16
+ return "session-123"
17
+
18
+ def close_session(self, sessionId: str):
19
+ self.calls.append(("close_session", sessionId))
20
+ return True
21
+
22
+ def list_local_sessions(self):
23
+ return ["session-123", "session-456"]
24
+
25
+ def call_tool(self, name: str, payload: Dict[str, Any]):
26
+ self.calls.append((name, payload))
27
+
28
+ if name == "screenshot":
29
+ return {
30
+ "content": [
31
+ {
32
+ "type": "image",
33
+ "mimeType": "image/png",
34
+ "data": "BASE64DATA",
35
+ }
36
+ ]
37
+ }
38
+
39
+ if name in ("create_browser", "release_browser"):
40
+ return {
41
+ "structuredContent": {
42
+ "success": True,
43
+ }
44
+ }
45
+
46
+ return {
47
+ "structuredContent": {
48
+ "ok": True,
49
+ "tool": name,
50
+ "payload": payload,
51
+ }
52
+ }
53
+
54
+
55
+ # ======================================================
56
+ # FIXTURES
57
+ # ======================================================
58
+
59
+ @pytest.fixture
60
+ def tools():
61
+ t = MCPTools()
62
+ t.client = MockMCPClient()
63
+ return t
64
+
65
+
66
+ # ======================================================
67
+ # SESSION MANAGEMENT
68
+ # ======================================================
69
+
70
+ def test_connect_mcp(tools):
71
+ res = tools.connect_mcp("http://localhost:3000")
72
+ assert res["ok"] is True
73
+
74
+
75
+ def test_create_session(tools):
76
+ res = tools.create_session("http://localhost:9222")
77
+ assert res["sessionId"] == "session-123"
78
+
79
+
80
+ def test_close_session(tools):
81
+ res = tools.close_session("session-123")
82
+ assert res["ok"] is True
83
+
84
+
85
+ def test_list_sessions(tools):
86
+ res = tools.list_sessions()
87
+ assert len(res["sessions"]) == 2
88
+
89
+
90
+ # ======================================================
91
+ # BROWSER RUNTIME (NO SESSION)
92
+ # ======================================================
93
+
94
+ def test_create_browser(tools):
95
+ res = tools.create_browser({"image": "chrome"})
96
+ assert res["success"] is True
97
+
98
+
99
+ def test_release_browser(tools):
100
+ res = tools.release_browser()
101
+ assert res["success"] is True
102
+
103
+
104
+ # ======================================================
105
+ # NAVIGATION & DOM
106
+ # ======================================================
107
+
108
+ def test_open_page(tools):
109
+ res = tools.open_page("session-123", "https://example.com")
110
+ assert res["ok"] is True
111
+
112
+
113
+ def test_get_html(tools):
114
+ res = tools.get_html("session-123")
115
+ assert res["ok"] is True
116
+
117
+
118
+ def test_screenshot(tools):
119
+ res = tools.screenshot("session-123")
120
+ assert res["type"] == "image"
121
+
122
+
123
+ def test_evaluate(tools):
124
+ res = tools.evaluate("session-123", "1+1")
125
+ assert res["ok"] is True
126
+
127
+
128
+ # ======================================================
129
+ # ELEMENT UTILITIES
130
+ # ======================================================
131
+
132
+ def test_find_element(tools):
133
+ res = tools.find_element("session-123", "#main")
134
+ assert res["ok"] is True
135
+
136
+
137
+ def test_find_all(tools):
138
+ res = tools.find_all("session-123", "div")
139
+ assert res["ok"] is True
140
+
141
+
142
+ def test_get_bounding_box(tools):
143
+ res = tools.get_bounding_box("session-123", "#box")
144
+ assert res["ok"] is True
145
+
146
+
147
+ def test_click_bounding_box(tools):
148
+ res = tools.click_bounding_box("session-123", "#btn")
149
+ assert res["ok"] is True
150
+
151
+
152
+ # ======================================================
153
+ # TAB MANAGEMENT
154
+ # ======================================================
155
+
156
+ def test_new_tab(tools):
157
+ res = tools.new_tab("session-123", "https://example.com")
158
+ assert res["ok"] is True
159
+
160
+
161
+ def test_switch_tab(tools):
162
+ res = tools.switch_tab("session-123", "tab-1")
163
+ assert res["ok"] is True
164
+
165
+
166
+ # ======================================================
167
+ # ADVANCED ACTIONS
168
+ # ======================================================
169
+
170
+ def test_click_to_text(tools):
171
+ res = tools.click_to_text("session-123", "Login")
172
+ assert res["ok"] is True
173
+
174
+
175
+ def test_find_element_xpath(tools):
176
+ res = tools.find_element_xpath("session-123", "//div")
177
+ assert res["ok"] is True
178
+
179
+
180
+ def test_find_element_by_text(tools):
181
+ res = tools.find_element_by_text("session-123", "Submit")
182
+ assert res["ok"] is True
183
+
184
+
185
+ def test_click_by_node_id(tools):
186
+ res = tools.click_by_node_id("session-123", 10)
187
+ assert res["ok"] is True
188
+
189
+
190
+ def test_import_cookies(tools):
191
+ res = tools.import_cookies("session-123", {"a": "b"})
192
+ assert res["ok"] is True
193
+
194
+
195
+ # ======================================================
196
+ # SCROLL / PERFORM
197
+ # ======================================================
198
+
199
+ def test_scroll_pixel(tools):
200
+ res = tools.scroll("session-123", y=500)
201
+ assert res["ok"] is True
202
+
203
+
204
+ def test_perform_drag(tools):
205
+ res = tools.drag_and_drop("session-123", 0, 0, 100, 100)
206
+ assert res["ok"] is True
File without changes
File without changes