# kl-mcp-client

SDK Python để gọi MCP Browser Server qua JSON-RPC HTTP.

## Cài đặt

```bash
pip install kl-mcp-client
```

## Thành phần

- `kl_mcp_client.client.MCPClient`: sync JSON-RPC client
- `kl_mcp_client.tools.MCPTools`: sync tools wrapper
- `kl_mcp_client.asyncio.client.MCPClient`: async JSON-RPC client
- `kl_mcp_client.asyncio.tools.MCPTools`: async tools wrapper

## Quickstart (sync)

```python
from kl_mcp_client.tools import MCPTools

tools = MCPTools()
tools.connect_mcp("http://localhost:3000/mcp")

sid = tools.create_session("http://localhost:9222")['sessionId']
tools.open_page(sid, "https://example.com")
print(tools.get_html(sid))
```

## Quickstart (async)

```python
import asyncio
from kl_mcp_client.asyncio.tools import MCPTools

async def main():
    tools = MCPTools()
    tools.connect_mcp("http://localhost:3000/mcp")

    sid = (await tools.create_session("http://localhost:9222"))["sessionId"]
    await tools.open_page(sid, "https://example.com")
    print(await tools.get_html(sid))
    await tools.close()

asyncio.run(main())
```

## Danh sách function (sync `kl_mcp_client.tools.MCPTools`)

### Kết nối / core
- `connect_mcp(mcp_url=None, mcpUrl=None, headers=None, timeout=30, retries=2, proxies=None)`: Khởi tạo kết nối tới MCP HTTP endpoint.
- `close()`: Đóng HTTP client sync.

### Session tools
- `create_session(cdpUrl)`: Tạo browser session từ CDP URL.
- `close_session(sessionId)`: Đóng session theo `sessionId`.
- `list_sessions()`: Liệt kê session cache cục bộ trong client.
- `open_page(sessionId, url)`: Điều hướng tab hiện tại tới URL.
- `get_html(sessionId)`: Lấy HTML hiện tại của trang.
- `click(sessionId, selector)`: Click phần tử theo CSS selector.
- `get_bounding_box(sessionId, selector)`: Lấy tọa độ/khung của phần tử.
- `click_to_text(sessionId, text)`: Click phần tử gần nhất theo nội dung text.
- `click_by_node_id(sessionId, nodeId)`: Click theo DOM `nodeId`.
- `screenshot(sessionId)`: Chụp ảnh màn hình tab hiện tại.
- `import_cookies(sessionId, cookies)`: Nạp danh sách cookies vào session.
- `click_bounding_box(sessionId, selector)`: Click tâm bounding box của selector.
- `upload_file(sessionId, selector, file_path)`: Upload file qua `/upload`, rồi gán vào input file.
- `perform(sessionId, action, target=None, value=None, x=None, y=None, from_point=None, to_point=None)`: API thao tác tổng quát cho click/drag/hover/... .
- `wait_for_selector(sessionId, selector, timeout=None, timeoutMs=None)`: Chờ selector xuất hiện/khả dụng.
- `scroll(sessionId, x=None, y=None, selector=None, position=None)`: Cuộn trang theo tọa độ, selector, hoặc vị trí top/bottom.
- `evaluate(sessionId, expression)`: Thực thi JavaScript expression trên trang.
- `evaluate_stream(sessionId, expression, chunkSize=100)`: Thực thi JS và trả kết quả dạng stream.
- `stream_pull(stream_id, offset=0, limit=100)`: Lấy thêm chunk dữ liệu từ stream.
- `get_cookies(sessionId)`: Lấy toàn bộ cookies hiện tại của session.
- `parse_html_by_prompt(html, prompt)`: Trích xuất dữ liệu từ HTML theo prompt.
- `viewport(sessionId, viewport=None)`: Lấy hoặc set viewport (nếu truyền `viewport`).

### Input tools
- `send_keys(sessionId, text, interval=None)`: Gửi chuỗi phím theo nhịp gõ.
- `type(sessionId, selector, text)`: Điền text vào input theo selector.
- `send_text(sessionId, text, interval=None)`: Gửi text thô tới trang đang focus.
- `clear_all(sessionId)`: Xóa nội dung input/selection đang active.
- `send_combo(sessionId, text)`: Gửi tổ hợp phím (vd `Ctrl+A`, `Ctrl+V`).

### Tab tools
- `get_current_url(sessionId)`: Lấy URL tab đang active.
- `list_tabs(sessionId)`: Liệt kê tất cả tab trong session.
- `switch_tab(sessionId, tabId=None, targetId=None)`: Chuyển tab theo `tabId` (hoặc alias `targetId`).
- `close_tab(sessionId, tabId)`: Đóng tab theo `tabId`.
- `new_tab(sessionId, url=None)`: Tạo tab mới, có thể mở URL ngay.
- `switch_tab_by_title(sessionId, title)`: Chuyển tab theo tiêu đề.
- `switch_tab_by_url(sessionId, url)`: Chuyển tab theo URL.
- `current_tab(sessionId)`: Lấy metadata tab hiện tại.

### DOM tools
- `get_dom_tree(sessionId)`: Lấy cây DOM đầy đủ.
- `get_clickable(sessionId)`: Lấy danh sách phần tử có thể click.
- `selector_map(sessionId, selector)`: Sinh map selector/metadata cho vùng DOM.
- `get_clean_text(sessionId)`: Lấy text đã làm sạch từ trang.

### Element tools
- `find_all(sessionId, selector)`: Tìm tất cả phần tử khớp selector.
- `find_element(sessionId, selector)`: Tìm phần tử đầu tiên khớp selector.
- `find_element_by_xpath(sessionId, xpath)`: Tìm phần tử bằng XPath.
- `find_element_xpath(sessionId, xpath)`: Alias tương thích cũ của `find_element_by_xpath`.
- `find_element_by_text(sessionId, text)`: Tìm phần tử theo text hiển thị.
- `find_element_by_prompt(sessionId, prompt)`: Tìm phần tử theo prompt ngôn ngữ tự nhiên.

### Browser tools
- `create_browser(payload=None)`: Tạo browser runtime mới từ MCP cluster.
- `release_browser(pod_name)`: Giải phóng browser runtime theo `pod_name`.

### Helper / tương thích ngược
- `cookies(sessionId)`: Alias của `get_cookies`.
- `current_url(sessionId)`: Alias của `get_current_url`.
- `set_viewport(sessionId, width, height, deviceScaleFactor=1.0, mobile=False)`: Helper set viewport nhanh.
- `get_viewport(sessionId)`: Helper lấy viewport hiện tại.
- `drag_and_drop(sessionId, from_x, from_y, to_x, to_y)`: Helper kéo-thả dựa trên `perform`.
- `hover(sessionId, x, y)`: Helper rê chuột tại tọa độ.
- `evaluate_stream_all(sessionId, expression, chunkSize=100, max_items=None)`: Helper pull toàn bộ stream thành list.

## JavaScript helper cho `evaluate(...)`

MCP extension inject [highlight.js](/home/kyle/projects/mcp-projects/mcp-extension/highlight.js) từ [content.js](/home/kyle/projects/mcp-projects/mcp-extension/content.js), nên trong page context có sẵn một số helper `window.__MCP_*` để gọi trực tiếp qua `evaluate(sessionId, expression)`.

Ví dụ:

```python
tools.evaluate(sid, """
(function() {
  // Lấy nội dung chính của trang, gồm title/description/content
  return window.__MCP_GET_CLEAN_TEXT();
})()
""")

tools.evaluate(sid, """
(function() {
  // Lấy text đang hiển thị trên màn hình sau khi đã loại phần ẩn
  return window.__MCP_GET_CLEAN_VISIBLE_TEXT();
})()
""")

tools.evaluate(sid, """
(function() {
  // Lấy danh sách phần tử có thể tương tác/click trong viewport
  return window.__MCP_GET_CLICKABLE_ELEMENTS();
})()
""")

tools.evaluate(sid, """
(function() {
  // Lấy metadata DOM cho tất cả phần tử match CSS selector
  return window.__MCP_GET_SELECTOR_MAP('button');
})()
""")

tools.evaluate(sid, """
(function() {
  // Build DOM tree nhưng không vẽ highlight lên trang
  return window.__MCP_GET_DOM_TREE({ doHighlightElements: false });
})()
""")
```

Các helper public hiện có:

| Hàm JS | Mô tả | Kết quả trả về |
|------|------|------|
| `window.__MCP_CLEAR_HIGHLIGHTS()` | Xóa toàn bộ overlay highlight trên trang | `undefined` |
| `window.__MCP_FOCUS_ELEMENT(element)` | Highlight một DOM element cụ thể | `undefined` |
| `window.__MCP_BUILD_DOM_TREE(args)` | Build DOM tree nội bộ, có thể kèm highlight | `{ rootId, map }` |
| `window.__MCP_GET_DOM_TREE(args)` | Wrapper an toàn cho `__MCP_BUILD_DOM_TREE` | `{ rootId, map }` hoặc `{ error, message? }` |
| `window.__MCP_GET_CLICKABLE_ELEMENTS(opts)` | Trả danh sách phần tử interactive đang visible kèm `xpath`, `rect`, `text`, `highlightIndex` | `Array[object]` |
| `window.__MCP_GET_SELECTOR_MAP(selector, opts)` | Trả metadata DOM tree cho các phần tử match CSS selector | `Array[object]` hoặc `{ error }` |
| `window.__MCP_GET_CLEAN_TEXT()` | Trích xuất text chính của trang, kèm `title`, `description`, marker link/image | `{ title, description, content, length }` |
| `window.__MCP_GET_CLEAN_VISIBLE_TEXT()` | Lấy visible text đã làm sạch từ DOM hiện tại | `{ text, length }` |

Ví dụ thực tế:

```python
# Lấy nội dung chính của trang
result = tools.evaluate(sid, """
(function() {
  // Trả về title, description, content và độ dài nội dung
  return window.__MCP_GET_CLEAN_TEXT();
})()
""")

# Lấy danh sách phần tử có thể click
result = tools.evaluate(sid, """
(function() {
  // Trả về danh sách element interactive đang visible
  return window.__MCP_GET_CLICKABLE_ELEMENTS();
})()
""")

# Highlight phần tử đầu tiên match selector
tools.evaluate(sid, """
(function() {
  // Tìm button đầu tiên rồi vẽ highlight để debug
  const el = document.querySelector('button');
  if (el) window.__MCP_FOCUS_ELEMENT(el);
  return { ok: !!el };
})()
""")

# Lấy metadata cho tất cả button
result = tools.evaluate(sid, """
(function() {
  // Trả về xpath, rect, text và metadata DOM tree của mọi button
  return window.__MCP_GET_SELECTOR_MAP('button');
})()
""")
```

Lưu ý:

- Các helper này chỉ có sau khi content script inject xong `highlight.js`.
- `content.js` hiện không expose thêm public API evaluate nào khác; `injectBrowserUseScreensaver(...)` chỉ là helper internal.
- Với `window.__MCP_FOCUS_ELEMENT(element)`, cần resolve DOM element ngay trong expression `evaluate(...)`, không truyền selector string trực tiếp.
- Với `evaluate(...)`, nên dùng expression dạng IIFE có `return`, ví dụ `"(function() { return window.__MCP_GET_CLEAN_TEXT(); })()"`.

## Danh sách function async

`kl_mcp_client.asyncio.tools.MCPTools` hỗ trợ cùng API và cùng ý nghĩa function như bản sync, nhưng tất cả lời gọi là bất đồng bộ (`await`).

## Lưu ý

- Cần gọi `connect_mcp(...)` trước khi gọi tools.
- `upload_file(...)` dùng endpoint `/upload` để lấy `uploadId`, sau đó gọi tool `uploadFile`.
- Tool name map theo server tại `mcp-server/internal/interfaces/http/mcp`.
