kl-mcp-client 1.0.0__py3-none-any.whl → 1.0.2__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.
@@ -0,0 +1,7 @@
1
+ from .client import MCPClient
2
+ from .tools import MCPTools
3
+
4
+ __all__ = [
5
+ "MCPClient",
6
+ "MCPTools",
7
+ ]
@@ -0,0 +1 @@
1
+ __VERSION__ = "1.0.0"
@@ -0,0 +1,7 @@
1
+ from .client import MCPClient
2
+ from .tools import MCPTools
3
+
4
+ __all__ = [
5
+ "MCPClient",
6
+ "MCPTools",
7
+ ]
@@ -0,0 +1,141 @@
1
+ # mcp_client_async.py
2
+ import uuid
3
+ import time
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import httpx
7
+ import asyncio
8
+
9
+
10
+ DEFAULT_HEADERS = {
11
+ "Content-Type": "application/json",
12
+ "Accept": "application/json, text/event-stream",
13
+ }
14
+
15
+
16
+ class MCPError(Exception):
17
+ pass
18
+
19
+
20
+ class MCPClient:
21
+ def __init__(
22
+ self,
23
+ base_url: str,
24
+ headers: Optional[Dict[str, str]] = None,
25
+ timeout: int = 30,
26
+ retries: int = 1,
27
+ ):
28
+ """
29
+ base_url: full MCP HTTP endpoint e.g. http://localhost:3000/mcp
30
+ headers: extra headers (e.g. {"Authorization": "Bearer ..."})
31
+ timeout: request timeout seconds
32
+ retries: number of attempts for network errors
33
+ """
34
+ self.base_url = base_url.rstrip("/")
35
+ self.headers = DEFAULT_HEADERS.copy()
36
+ if headers:
37
+ self.headers.update(headers)
38
+
39
+ self.timeout = timeout
40
+ self.retries = max(1, retries)
41
+
42
+ self._client = httpx.AsyncClient(
43
+ timeout=self.timeout,
44
+ headers=self.headers,
45
+ )
46
+
47
+ # local session cache
48
+ self._sessions: Dict[str, Dict[str, Any]] = {}
49
+
50
+ async def _rpc(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
51
+ payload = {
52
+ "jsonrpc": "2.0",
53
+ "id": str(uuid.uuid4()),
54
+ "method": method,
55
+ "params": params,
56
+ }
57
+
58
+ last_exc = None
59
+ for attempt in range(self.retries):
60
+ try:
61
+ r = await self._client.post(
62
+ self.base_url,
63
+ json=payload,
64
+ )
65
+ r.raise_for_status()
66
+ except Exception as e:
67
+ last_exc = e
68
+ await asyncio.sleep(0.2 * (attempt + 1))
69
+ continue
70
+
71
+ try:
72
+ data = r.json()
73
+ except ValueError:
74
+ raise MCPError(
75
+ f"Invalid JSON response (status {r.status_code}): {r.text}"
76
+ )
77
+
78
+ if err := data.get("error"):
79
+ raise MCPError(
80
+ {
81
+ "code": err.get("code"),
82
+ "message": err.get("message"),
83
+ "data": err.get("data"),
84
+ }
85
+ )
86
+
87
+ return data.get("result", {})
88
+
89
+ raise MCPError(f"Request failed after {self.retries} attempts: {last_exc}")
90
+
91
+ # -------------------------
92
+ # Generic tool wrappers
93
+ # -------------------------
94
+
95
+ async def call_tool_structured(
96
+ self, tool: str, arguments: Optional[Dict[str, Any]] = None
97
+ ) -> Dict[str, Any]:
98
+ res = await self._rpc("callTool", {"tool": tool, "arguments": arguments or {}})
99
+
100
+ if isinstance(res, dict) and "structuredContent" in res:
101
+ return res["structuredContent"]
102
+
103
+ return res
104
+
105
+ async def call_tool(
106
+ self, tool: str, arguments: Optional[Dict[str, Any]] = None
107
+ ) -> Dict[str, Any]:
108
+ return await self._rpc("callTool", {"tool": tool, "arguments": arguments or {}})
109
+
110
+ # -------------------------
111
+ # Session helpers
112
+ # -------------------------
113
+
114
+ async def create_session(self, cdpUrl: str) -> str:
115
+ result = await self.call_tool_structured("createSession", {"cdpUrl": cdpUrl})
116
+
117
+ session_id = (
118
+ result.get("sessionId") or result.get("session_id") or result.get("id")
119
+ )
120
+
121
+ if not session_id:
122
+ raise MCPError("createSession did not return sessionId")
123
+
124
+ self._sessions[session_id] = {"created_at": time.time()}
125
+ return session_id
126
+
127
+ async def close_session(self, session_id: str) -> bool:
128
+ try:
129
+ await self.call_tool_structured("closeSession", {"sessionId": session_id})
130
+ self._sessions.pop(session_id, None)
131
+ return True
132
+ except MCPError:
133
+ self._sessions.pop(session_id, None)
134
+ raise
135
+
136
+ def list_local_sessions(self) -> List[str]:
137
+ return list(self._sessions.keys())
138
+
139
+ async def aclose(self):
140
+ """Close the httpx async client"""
141
+ await self._client.aclose()
@@ -0,0 +1,179 @@
1
+ # async_tools.py
2
+ from typing import Any, Dict, Optional
3
+
4
+ from .client import MCPClient
5
+
6
+
7
+ class MCPTools:
8
+ """
9
+ Async wrapper cho Google ADK + MCP Server.
10
+ - Dùng MCPClientAsync (httpx async)
11
+ - Tất cả method đều async
12
+ - Screenshot trả về đúng format ADK Web yêu cầu
13
+ """
14
+
15
+ def __init__(self, client: MCPClient):
16
+ self.client = client
17
+
18
+ # ======================================================
19
+ # SESSION MANAGEMENT
20
+ # ======================================================
21
+
22
+ async def create_session(self, cdpUrl: str) -> Dict[str, Any]:
23
+ sid = await self.client.create_session(cdpUrl)
24
+ return {"sessionId": sid}
25
+
26
+ async def close_session(self, sessionId: str) -> Dict[str, Any]:
27
+ ok = await self.client.close_session(sessionId)
28
+ return {"ok": bool(ok)}
29
+
30
+ async def list_sessions(self) -> Dict[str, Any]:
31
+ return {"sessions": self.client.list_local_sessions()}
32
+
33
+ # ======================================================
34
+ # NAVIGATION & DOM
35
+ # ======================================================
36
+
37
+ async def open_page(self, sessionId: str, url: str) -> Dict[str, Any]:
38
+ res = await self.client.call_tool(
39
+ "openPage", {"sessionId": sessionId, "url": url}
40
+ )
41
+ return res.get("structuredContent", {})
42
+
43
+ async def get_html(self, sessionId: str) -> Dict[str, Any]:
44
+ res = await self.client.call_tool("getHTML", {"sessionId": sessionId})
45
+ return res.get("structuredContent", {})
46
+
47
+ async def screenshot(self, sessionId: str) -> Dict[str, Any]:
48
+ """
49
+ ADK Web expects this structure:
50
+ {
51
+ "type": "image",
52
+ "mimeType": "image/png",
53
+ "data": "<base64>"
54
+ }
55
+ """
56
+ res = await self.client.call_tool("screenshot", {"sessionId": sessionId})
57
+ return res["content"][0]
58
+
59
+ async def click(self, sessionId: str, selector: str) -> Dict[str, Any]:
60
+ res = await self.client.call_tool(
61
+ "click", {"sessionId": sessionId, "selector": selector}
62
+ )
63
+ return res.get("structuredContent", {})
64
+
65
+ async def type(self, sessionId: str, selector: str, text: str) -> Dict[str, Any]:
66
+ res = await self.client.call_tool(
67
+ "type", {"sessionId": sessionId, "selector": selector, "text": text}
68
+ )
69
+ return res.get("structuredContent", {})
70
+
71
+ async def evaluate(self, sessionId: str, expression: str) -> Dict[str, Any]:
72
+ res = await self.client.call_tool(
73
+ "evaluate", {"sessionId": sessionId, "expression": expression}
74
+ )
75
+ return res.get("structuredContent", {})
76
+
77
+ # ======================================================
78
+ # ELEMENT UTILITIES
79
+ # ======================================================
80
+
81
+ async def find_element(self, sessionId: str, selector: str) -> Dict[str, Any]:
82
+ res = await self.client.call_tool(
83
+ "findElement", {"sessionId": sessionId, "selector": selector}
84
+ )
85
+ return res.get("structuredContent", {})
86
+
87
+ async def find_all(self, sessionId: str, selector: str) -> Dict[str, Any]:
88
+ res = await self.client.call_tool(
89
+ "findAll", {"sessionId": sessionId, "selector": selector}
90
+ )
91
+ return res.get("structuredContent", {})
92
+
93
+ async def get_bounding_box(self, sessionId: str, selector: str) -> Dict[str, Any]:
94
+ res = await self.client.call_tool(
95
+ "getBoundingBox", {"sessionId": sessionId, "selector": selector}
96
+ )
97
+ return res.get("structuredContent", {})
98
+
99
+ async def click_bounding_box(self, sessionId: str, selector: str) -> Dict[str, Any]:
100
+ res = await self.client.call_tool(
101
+ "clickBoundingBox", {"sessionId": sessionId, "selector": selector}
102
+ )
103
+ return res.get("structuredContent", {})
104
+
105
+ async def upload_file(
106
+ self, sessionId: str, selector: str, filename: str, base64data: str
107
+ ) -> Dict[str, Any]:
108
+ res = await self.client.call_tool(
109
+ "uploadFile",
110
+ {
111
+ "sessionId": sessionId,
112
+ "selector": selector,
113
+ "filename": filename,
114
+ "data": base64data,
115
+ },
116
+ )
117
+ return res.get("structuredContent", {})
118
+
119
+ async def wait_for_selector(
120
+ self, sessionId: str, selector: str, timeoutMs: Optional[int] = None
121
+ ) -> Dict[str, Any]:
122
+ args = {"sessionId": sessionId, "selector": selector}
123
+ if timeoutMs is not None:
124
+ args["timeoutMs"] = timeoutMs
125
+
126
+ res = await self.client.call_tool("waitForSelector", args)
127
+ return res.get("structuredContent", {})
128
+
129
+ # ======================================================
130
+ # TAB MANAGEMENT
131
+ # ======================================================
132
+
133
+ async def new_tab(
134
+ self, sessionId: str, url: Optional[str] = "about:blank"
135
+ ) -> Dict[str, Any]:
136
+ res = await self.client.call_tool(
137
+ "newTab", {"sessionId": sessionId, "url": url}
138
+ )
139
+ return res.get("structuredContent", {})
140
+
141
+ async def switch_tab(self, sessionId: str, targetId: str) -> Dict[str, Any]:
142
+ res = await self.client.call_tool(
143
+ "switchTab", {"sessionId": sessionId, "targetId": targetId}
144
+ )
145
+ return res.get("structuredContent", {})
146
+
147
+ # ======================================================
148
+ # ADVANCED
149
+ # ======================================================
150
+
151
+ async def click_to_text(self, sessionId: str, text: str) -> dict:
152
+ res = await self.client.call_tool(
153
+ "clickToText", {"sessionId": sessionId, "text": text}
154
+ )
155
+ return res.get("structuredContent", {})
156
+
157
+ async def find_element_xpath(self, sessionId: str, xpath: str) -> Dict[str, Any]:
158
+ res = await self.client.call_tool(
159
+ "findElementByXPath", {"sessionId": sessionId, "xpath": xpath}
160
+ )
161
+ return res.get("structuredContent", {})
162
+
163
+ async def find_element_by_text(self, sessionId: str, text: str) -> Dict[str, Any]:
164
+ res = await self.client.call_tool(
165
+ "findElementByText", {"sessionId": sessionId, "text": text}
166
+ )
167
+ return res.get("structuredContent", {})
168
+
169
+ async def click_by_node_id(self, sessionId: str, nodeId: int) -> Dict[str, Any]:
170
+ res = await self.client.call_tool(
171
+ "clickByNodeId", {"sessionId": sessionId, "nodeId": nodeId}
172
+ )
173
+ return res.get("structuredContent", {})
174
+
175
+ async def import_cookies(self, sessionId: str, cookies: dict) -> Dict[str, Any]:
176
+ res = await self.client.call_tool(
177
+ "importCookies", {"sessionId": sessionId, "cookies": cookies}
178
+ )
179
+ return res.get("structuredContent", {})
@@ -0,0 +1,251 @@
1
+ Metadata-Version: 2.4
2
+ Name: kl-mcp-client
3
+ Version: 1.0.2
4
+ Summary: MCP Client
5
+ Author-email: Kyle <hngan.it@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: requests>=2.32.5
10
+ Requires-Dist: httpx>=0.28.1
11
+
12
+ # 📦 kl-mcp-client
13
+
14
+ **kl-mcp-client** là SDK Python giúp giao tiếp với **MCP (Model Context Protocol) Browser Server** — một server trung gian điều khiển Chrome/Chromium qua CDP.
15
+
16
+ Thư viện này được thiết kế để:
17
+
18
+ - Điều khiển Chrome tự động: click, nhập liệu, screenshot, đọc DOM…
19
+ - Kết nối trình duyệt Chrome Remote qua CDP (Chrome DevTools Protocol)
20
+ - Tích hợp làm Web Automation Agent trong **Google ADK**
21
+ - Hoạt động độc lập như một browser automation SDK
22
+
23
+ ---
24
+
25
+ # 🚀 Cài đặt
26
+
27
+ ```bash
28
+ pip install kl-mcp-client
29
+ ```
30
+
31
+ ---
32
+
33
+ # 🧩 Thành phần của thư viện
34
+
35
+ Package gồm 2 module chính:
36
+
37
+ | File | Vai trò |
38
+ |------|---------|
39
+ | `client.py` | JSON-RPC HTTP Client giao tiếp với MCP Server |
40
+ | `tools.py` | Wrapper cấp cao, cung cấp API tiện dụng cho các Agent & automation |
41
+
42
+ ---
43
+
44
+ # ✨ Tính năng chính
45
+
46
+ ### ✔ Điều khiển trình duyệt
47
+ - Mở tab, load URL
48
+ - Click CSS selector, click bằng text, click bằng nodeId
49
+ - Nhập text vào input
50
+ - Screenshot (chuẩn ADK hiển thị được)
51
+
52
+ ### ✔ DOM Tools nâng cao
53
+ - Find element by selector / text / XPath
54
+ - Lấy bounding box
55
+ - Lấy toàn bộ DOM Tree
56
+ - Lấy danh sách clickable elements
57
+ - Highlight elements (nếu server hỗ trợ)
58
+
59
+ ### ✔ Tương tác hệ thống
60
+ - Upload file bằng base64
61
+ - Import cookies
62
+ - Evaluate JavaScript
63
+
64
+ ### ✔ Chrome Remote Debugging
65
+ - Tạo session từ địa chỉ:
66
+ `http://localhost:9222/json/version`
67
+
68
+ ---
69
+
70
+ # 🧭 Cách sử dụng
71
+
72
+ ## 1. Import client & tools
73
+
74
+ ```python
75
+ from kl_mcp_client.client import MCPClient
76
+ from kl_mcp_client.tools import MCPTools
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 2. Tạo kết nối tới MCP Server
82
+
83
+ ```python
84
+ mcp = MCPClient(
85
+ base_url="http://localhost:3000/mcp",
86
+ timeout=30,
87
+ retries=2
88
+ )
89
+ tools = MCPTools(mcp)
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 3. Tạo session (Chrome Remote)
95
+
96
+ ```python
97
+ session = tools.create_session("http://localhost:9222/json/version")
98
+ sid = session["sessionId"]
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 4. Mở URL
104
+
105
+ ```python
106
+ tools.open_page(sid, "https://google.com")
107
+ ```
108
+
109
+ ---
110
+
111
+ ## 5. Screenshot (hiển thị được trong ADK Web)
112
+
113
+ ```python
114
+ img = tools.screenshot(sid)
115
+ print(img)
116
+ ```
117
+
118
+ Trả về:
119
+
120
+ ```json
121
+ {
122
+ "type": "image",
123
+ "mimeType": "image/png",
124
+ "data": "<base64>"
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 6. Click & Type
131
+
132
+ ```python
133
+ tools.click(sid, "#login")
134
+ tools.type(sid, "input[name=q]", "Hello world")
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 7. Find Elements
140
+
141
+ ```python
142
+ tools.find_element(sid, "#content")
143
+ tools.find_element_by_text(sid, "Đăng nhập")
144
+ tools.find_element_xpath(sid, "//input[@type='email']")
145
+ ```
146
+
147
+ ---
148
+
149
+ ## 8. Upload File
150
+
151
+ ```python
152
+ import base64
153
+ data = base64.b64encode(open("test.pdf", "rb").read()).decode()
154
+
155
+ tools.upload_file(
156
+ sid,
157
+ selector="input[type=file]",
158
+ filename="test.pdf",
159
+ base64Data=data
160
+ )
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 9. Import Cookies
166
+
167
+ ```python
168
+ tools.import_cookies(sid, [
169
+ {"name": "token", "value": "abc", "domain": "example.com", "path": "/"}
170
+ ])
171
+ ```
172
+
173
+ ---
174
+
175
+ ## 10. Đóng session
176
+
177
+ ```python
178
+ tools.close_session(sid)
179
+ ```
180
+
181
+ ---
182
+
183
+ # 🧪 Ví dụ đầy đủ
184
+
185
+ ```python
186
+ from kl_mcp_client.client import MCPClient
187
+ from kl_mcp_client.tools import MCPTools
188
+ import base64
189
+
190
+ mcp = MCPClient("http://localhost:3000/mcp")
191
+ tools = MCPTools(mcp)
192
+
193
+ # Create session
194
+ sid = tools.create_session("http://localhost:9222/json/version")["sessionId"]
195
+
196
+ # Navigate
197
+ tools.open_page(sid, "https://google.com")
198
+
199
+ # Screenshot
200
+ img = tools.screenshot(sid)
201
+ print("Screenshot returned:", img["mimeType"])
202
+
203
+ # Search
204
+ tools.type(sid, "input[name=q]", "Hello MCP")
205
+ tools.click_to_text(sid, "Google Search")
206
+
207
+ # Close
208
+ tools.close_session(sid)
209
+ ```
210
+
211
+ ---
212
+
213
+ # 🏗 Kiến trúc
214
+
215
+ ```
216
+ Python App / ADK Agent
217
+
218
+ kl-mcp-client
219
+
220
+ MCP Browser Server
221
+
222
+ Chrome / CDP
223
+ ```
224
+
225
+ ---
226
+
227
+ # 📘 Yêu cầu
228
+
229
+ - Python ≥ 3.8
230
+ - MCP Server chạy sẵn (Chromedp backend)
231
+ - Chrome/Chromium với cờ:
232
+
233
+ ```
234
+ chrome.exe --remote-debugging-port=9222
235
+ ```
236
+
237
+ ---
238
+
239
+ # 📝 License
240
+
241
+ MIT License.
242
+
243
+ ---
244
+
245
+ # 📚 Liên hệ
246
+
247
+ Nếu bạn cần:
248
+
249
+ - MCP Server đầy đủ
250
+ - Hỗ trợ tích hợp ADK Web Agent
251
+ - Thêm tool: DOM Tree, highlight, selector map…
@@ -0,0 +1,11 @@
1
+ kl_mcp_client/__init__.py,sha256=pdJdBcEH5HaRuHSe2B7VUyRgH5ad3u1dDc4euZMDRMY,106
2
+ kl_mcp_client/__version__.py,sha256=CCYQUt19bi0kfo0Q8q8snnwgw_c9F3s6FFxTMsX1yDE,22
3
+ kl_mcp_client/client.py,sha256=0kkC1oGS4lMbQmsNtKa7fp_2aZdcqVZLssTmsVKmai0,4668
4
+ kl_mcp_client/tools.py,sha256=GC5IRYnWH35wBNCMrFGYE8VQx4bhj5uiJNDPBjDfdvQ,6615
5
+ kl_mcp_client/async/__init__.py,sha256=pdJdBcEH5HaRuHSe2B7VUyRgH5ad3u1dDc4euZMDRMY,106
6
+ kl_mcp_client/async/client.py,sha256=bRpDmDU4FhehKWfQ1ftVPSQ7tSioR4GrpD3NX_msRqc,4131
7
+ kl_mcp_client/async/tools.py,sha256=98L9JM5QM1jST-ICFACNc5Uwekaj6Y60ZU51-fRfaaY,6785
8
+ kl_mcp_client-1.0.2.dist-info/METADATA,sha256=lsT9pIjx9cFaKJ3cB3p2-M8HeMao8eYoPBudVj4xl0k,4432
9
+ kl_mcp_client-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ kl_mcp_client-1.0.2.dist-info/top_level.txt,sha256=wd_HFFyGjiKavwACuj8Ny0svtVyNsrxCSVU48EkoQ7c,14
11
+ kl_mcp_client-1.0.2.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ kl_mcp_client
@@ -1,8 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: kl-mcp-client
3
- Version: 1.0.0
4
- Summary: MCP Client
5
- Author-email: Kyle <hngan.it@gmail.com>
6
- License: MIT
7
- Requires-Python: >=3.7
8
- Description-Content-Type: text/markdown
@@ -1,7 +0,0 @@
1
- mcp_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mcp_client/client.py,sha256=0kkC1oGS4lMbQmsNtKa7fp_2aZdcqVZLssTmsVKmai0,4668
3
- mcp_client/tools.py,sha256=GC5IRYnWH35wBNCMrFGYE8VQx4bhj5uiJNDPBjDfdvQ,6615
4
- kl_mcp_client-1.0.0.dist-info/METADATA,sha256=3NUizw9TxUkbninPSWsL0jSiy9FxWNnSB3J6ZMVpls8,193
5
- kl_mcp_client-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- kl_mcp_client-1.0.0.dist-info/top_level.txt,sha256=ZSWRi9Sxwn8pf9wHQny6lLfmYSfG4JpbzGdVrwvQoxM,11
7
- kl_mcp_client-1.0.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- mcp_client
mcp_client/__init__.py DELETED
File without changes
File without changes
File without changes