kl-mcp-client 2.1.0__tar.gz → 2.1.3__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.0
3
+ Version: 2.1.3
4
4
  Summary: MCP Client for Python
5
5
  Author-email: Kyle <hngan.it@gmail.com>
6
6
  License: MIT
@@ -146,3 +146,4 @@ class MCPClient:
146
146
  async def aclose(self):
147
147
  """Close the httpx async client"""
148
148
  await self._client.aclose()
149
+
@@ -272,3 +272,50 @@ class MCPTools:
272
272
  },
273
273
  )
274
274
  return res.get("structuredContent", {})
275
+
276
+ # CLEAN TEXT / READ MODE
277
+ # ======================================================
278
+ async def get_clean_text(self, sessionId: str) -> Dict[str, Any]:
279
+ """
280
+ Lấy toàn bộ visible text đã được clean trên trang hiện tại.
281
+ - Bỏ script/style/iframe/svg/canvas
282
+ - Chỉ text nhìn thấy (display/visibility/opacity)
283
+
284
+ Returns:
285
+ {
286
+ "text": "...",
287
+ "length": 12345
288
+ }
289
+ """
290
+ res = await self.client.call_tool(
291
+ "getCleanText",
292
+ {"sessionId": sessionId},
293
+ )
294
+ return res.get("structuredContent", {})
295
+
296
+ # ======================================================
297
+ # KEYBOARD (ASYNC)
298
+ # ======================================================
299
+ async def send_key(
300
+ self,
301
+ sessionId: str,
302
+ key: str,
303
+ ) -> Dict[str, Any]:
304
+ """
305
+ Send a keyboard key to the active page (async).
306
+
307
+ Args:
308
+ sessionId: MCP browser session
309
+ key: Keyboard key (e.g. "Enter", "Tab", "Escape", "ArrowDown",
310
+ "Ctrl+a", "Ctrl+Enter")
311
+
312
+ Returns:
313
+ structuredContent from MCP server
314
+ """
315
+ return await self.call_tool_structured(
316
+ "sendKey",
317
+ {
318
+ "sessionId": sessionId,
319
+ "key": key,
320
+ },
321
+ )
@@ -126,27 +126,63 @@ class MCPTools:
126
126
 
127
127
  @_ensure_client
128
128
  def upload_file(
129
- self, sessionId: str, selector: str, filename: str, base64data: str
129
+ self,
130
+ sessionId: str,
131
+ selector: str,
132
+ file_path: str,
130
133
  ) -> Dict[str, Any]:
131
134
  """
132
- Upload file vào input[type=file]
135
+ Upload file (kể cả video lớn) vào input[type=file] theo luồng mới:
136
+ 1. Multipart upload file lên MCP server
137
+ 2. Nhận uploadId
138
+ 3. Gọi MCP tool uploadFile với uploadId
139
+
133
140
  Args:
134
141
  sessionId: MCP browser session
135
142
  selector: CSS selector, ví dụ 'input[type=file]'
136
- filename: tên file trên browser side
137
- base64data: dữ liệu base64 (không kèm header)
138
- Returns:
139
- structured result từ server
143
+ file_path: đường dẫn file local (video, pdf, doc, ...)
140
144
  """
141
- return self.client.call_tool(
145
+
146
+ if not file_path:
147
+ return {"ok": False, "error": "file_path is required"}
148
+
149
+ # --------------------------------------------------
150
+ # 1️⃣ Multipart upload file lên MCP server
151
+ # --------------------------------------------------
152
+ try:
153
+ with open(file_path, "rb") as f:
154
+ resp = self.client.http.post(
155
+ "/upload",
156
+ files={"file": f},
157
+ timeout=300, # upload file lớn
158
+ )
159
+ except Exception as e:
160
+ return {"ok": False, "error": f"upload http failed: {e}"}
161
+
162
+ if resp.status_code != 200:
163
+ return {
164
+ "ok": False,
165
+ "error": f"upload http error {resp.status_code}: {resp.text}",
166
+ }
167
+
168
+ data = resp.json()
169
+ upload_id = data.get("uploadId")
170
+ if not upload_id:
171
+ return {"ok": False, "error": "uploadId not returned from server"}
172
+
173
+ # --------------------------------------------------
174
+ # 2️⃣ Gọi MCP tool uploadFile (PATH MODE)
175
+ # --------------------------------------------------
176
+ result = self.client.call_tool(
142
177
  "uploadFile",
143
178
  {
144
179
  "sessionId": sessionId,
145
180
  "selector": selector,
146
- "filename": filename,
147
- "data": base64data,
181
+ "uploadId": upload_id,
148
182
  },
149
- ).get("structuredContent", {})
183
+ )
184
+
185
+ return result.get("structuredContent", {})
150
186
 
151
187
  @_ensure_client
152
188
  def wait_for_selector(
@@ -229,6 +265,16 @@ class MCPTools:
229
265
  {"sessionId": sessionId, "selector": selector, "args": args or {}},
230
266
  )
231
267
 
268
+ @_ensure_client
269
+ def find_element_by_prompt(self, sessionId: str, prompt: str) -> Dict[str, Any]:
270
+ """
271
+ Gọi tool findElementByPrompt trên MCP server.
272
+ Trả về structuredContent gồm: html, nodeId.
273
+ """
274
+ return self.client.call_tool(
275
+ "findElementByPrompt", {"sessionId": sessionId, "prompt": prompt}
276
+ ).get("structuredContent", {})
277
+
232
278
  # ======================================================
233
279
  # AI / CONTENT PARSING
234
280
  # ======================================================
@@ -254,65 +300,125 @@ class MCPTools:
254
300
  "prompt": prompt,
255
301
  },
256
302
  ).get("structuredContent", {})
303
+
257
304
  # ======================================================
258
- # MOUSE / PERFORM ACTIONS
305
+ # CLEAN TEXT / READ MODE
259
306
  # ======================================================
260
307
  @_ensure_client
261
- def perform_click_xy(
262
- self,
263
- sessionId: str,
264
- x: float,
265
- y: float,
266
- ) -> Dict[str, Any]:
308
+ def get_clean_text(self, sessionId: str) -> Dict[str, Any]:
267
309
  """
268
- Move mouse smoothly to (x, y) and left click.
310
+ Lấy toàn bộ visible text đã được clean trên trang hiện tại.
311
+ - Bỏ script/style/iframe/svg/canvas
312
+ - Chỉ text nhìn thấy (display/visibility/opacity)
313
+
314
+ Returns:
315
+ {
316
+ "text": "...",
317
+ "length": 12345
318
+ }
269
319
  """
270
320
  return self.client.call_tool(
271
- "perform",
272
- {
273
- "sessionId": sessionId,
274
- "action": "click",
275
- "x": float(x),
276
- "y": float(y),
277
- },
321
+ "getCleanText",
322
+ {"sessionId": sessionId},
278
323
  ).get("structuredContent", {})
324
+
279
325
  @_ensure_client
280
- def perform_drag(
326
+ def evaluate_stream(
281
327
  self,
282
328
  sessionId: str,
283
- from_x: float,
284
- from_y: float,
285
- to_x: float,
286
- to_y: float,
329
+ expression: str,
330
+ chunkSize: int = 100,
287
331
  ) -> Dict[str, Any]:
288
332
  """
289
- Drag mouse from (from_x, from_y) to (to_x, to_y).
333
+ Evaluate JS expression theo chế độ STREAM.
334
+ Dùng khi kết quả là array lớn (DOM, list, table, ...)
335
+
336
+ Returns (init response):
337
+ {
338
+ "stream_id": "...",
339
+ "total": 1234,
340
+ "chunk_size": 100
341
+ }
290
342
  """
291
343
  return self.client.call_tool(
292
- "perform",
344
+ "evaluate.stream",
293
345
  {
294
346
  "sessionId": sessionId,
295
- "action": "drag",
296
- "from": {"x": float(from_x), "y": float(from_y)},
297
- "to": {"x": float(to_x), "y": float(to_y)},
347
+ "expression": expression,
348
+ "chunkSize": int(chunkSize),
298
349
  },
299
350
  ).get("structuredContent", {})
351
+
300
352
  @_ensure_client
301
- def perform_hover(
353
+ def stream_pull(
302
354
  self,
303
- sessionId: str,
304
- x: float,
305
- y: float,
355
+ stream_id: str,
356
+ offset: int = 0,
357
+ limit: int = 100,
306
358
  ) -> Dict[str, Any]:
307
359
  """
308
- Move mouse smoothly to (x, y) without clicking.
360
+ Kéo 1 chunk từ stream đã tạo bởi evaluate.stream
361
+
362
+ Returns:
363
+ {
364
+ "items": [...],
365
+ "offset": 0,
366
+ "has_more": true
367
+ }
309
368
  """
310
369
  return self.client.call_tool(
311
- "perform",
370
+ "stream.pull",
312
371
  {
313
- "sessionId": sessionId,
314
- "action": "hover",
315
- "x": float(x),
316
- "y": float(y),
372
+ "stream_id": stream_id,
373
+ "offset": int(offset),
374
+ "limit": int(limit),
317
375
  },
318
376
  ).get("structuredContent", {})
377
+
378
+ @_ensure_client
379
+ def evaluate_stream_all(
380
+ self,
381
+ sessionId: str,
382
+ expression: str,
383
+ chunkSize: int = 100,
384
+ max_items: Optional[int] = None,
385
+ ):
386
+ """
387
+ Helper: evaluate.stream + tự động pull toàn bộ dữ liệu.
388
+
389
+ ⚠️ Chỉ dùng khi bạn THỰC SỰ cần full data.
390
+ """
391
+ init = self.evaluate_stream(
392
+ sessionId=sessionId,
393
+ expression=expression,
394
+ chunkSize=chunkSize,
395
+ )
396
+
397
+ stream_id = init.get("stream_id")
398
+ total = init.get("total", 0)
399
+
400
+ if not stream_id:
401
+ return []
402
+
403
+ items = []
404
+ offset = 0
405
+
406
+ while True:
407
+ chunk = self.stream_pull(
408
+ stream_id=stream_id,
409
+ offset=offset,
410
+ limit=chunkSize,
411
+ )
412
+
413
+ part = chunk.get("items", [])
414
+ items.extend(part)
415
+
416
+ if max_items and len(items) >= max_items:
417
+ return items[:max_items]
418
+
419
+ if not chunk.get("has_more"):
420
+ break
421
+
422
+ offset += chunkSize
423
+
424
+ return items
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kl-mcp-client
3
- Version: 2.1.0
3
+ Version: 2.1.3
4
4
  Summary: MCP Client for Python
5
5
  Author-email: Kyle <hngan.it@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kl-mcp-client"
7
- version = "2.1.0"
7
+ version = "2.1.3"
8
8
  description = "MCP Client for Python"
9
9
  dependencies = [ "requests>=2.32.5", "httpx>=0.28.1",]
10
10
  readme = "README.md"
File without changes
File without changes