kl-mcp-client 1.0.6__tar.gz → 1.2.1__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,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kl-mcp-client
3
- Version: 1.0.6
4
- Summary: MCP Client
3
+ Version: 1.2.1
4
+ Summary: MCP Client for Python
5
5
  Author-email: Kyle <hngan.it@gmail.com>
6
6
  License: MIT
7
7
  Requires-Python: >=3.8
@@ -1,11 +1,10 @@
1
1
  # mcp_client_async.py
2
2
  import uuid
3
3
  import time
4
+ import asyncio
4
5
  from typing import Any, Dict, List, Optional
5
6
 
6
7
  import httpx
7
- import asyncio
8
-
9
8
 
10
9
  DEFAULT_HEADERS = {
11
10
  "Content-Type": "application/json",
@@ -24,12 +23,18 @@ class MCPClient:
24
23
  headers: Optional[Dict[str, str]] = None,
25
24
  timeout: int = 30,
26
25
  retries: int = 1,
26
+ proxies: Optional[Dict[str, str]] = None,
27
27
  ):
28
28
  """
29
29
  base_url: full MCP HTTP endpoint e.g. http://localhost:3000/mcp
30
30
  headers: extra headers (e.g. {"Authorization": "Bearer ..."})
31
31
  timeout: request timeout seconds
32
32
  retries: number of attempts for network errors
33
+ proxies: httpx proxies dict, e.g.
34
+ {
35
+ "http://": "http://127.0.0.1:8080",
36
+ "https://": "http://127.0.0.1:8080"
37
+ }
33
38
  """
34
39
  self.base_url = base_url.rstrip("/")
35
40
  self.headers = DEFAULT_HEADERS.copy()
@@ -40,13 +45,17 @@ class MCPClient:
40
45
  self.retries = max(1, retries)
41
46
 
42
47
  self._client = httpx.AsyncClient(
43
- timeout=self.timeout,
44
48
  headers=self.headers,
49
+ timeout=httpx.Timeout(self.timeout),
50
+ proxies=proxies,
45
51
  )
46
52
 
47
53
  # local session cache
48
54
  self._sessions: Dict[str, Dict[str, Any]] = {}
49
55
 
56
+ # -------------------------
57
+ # JSON-RPC core
58
+ # -------------------------
50
59
  async def _rpc(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
51
60
  payload = {
52
61
  "jsonrpc": "2.0",
@@ -55,15 +64,13 @@ class MCPClient:
55
64
  "params": params,
56
65
  }
57
66
 
58
- last_exc = None
67
+ last_exc: Optional[Exception] = None
68
+
59
69
  for attempt in range(self.retries):
60
70
  try:
61
- r = await self._client.post(
62
- self.base_url,
63
- json=payload,
64
- )
71
+ r = await self._client.post(self.base_url, json=payload)
65
72
  r.raise_for_status()
66
- except Exception as e:
73
+ except httpx.HTTPError as e:
67
74
  last_exc = e
68
75
  await asyncio.sleep(0.2 * (attempt + 1))
69
76
  continue
@@ -89,7 +96,7 @@ class MCPClient:
89
96
  raise MCPError(f"Request failed after {self.retries} attempts: {last_exc}")
90
97
 
91
98
  # -------------------------
92
- # Generic tool wrappers
99
+ # Tool wrappers
93
100
  # -------------------------
94
101
 
95
102
  async def call_tool_structured(
@@ -177,3 +177,26 @@ class MCPTools:
177
177
  "importCookies", {"sessionId": sessionId, "cookies": cookies}
178
178
  )
179
179
  return res.get("structuredContent", {})
180
+
181
+ async def parse_html_by_prompt(self, html: str, prompt: str) -> Dict[str, Any]:
182
+ """
183
+ Parse HTML content using AI with dynamic prompt-defined structure.
184
+
185
+ Args:
186
+ html: Raw HTML string (client-provided)
187
+ prompt: Instruction that defines what to extract and output structure
188
+ Example:
189
+ - "Hãy lấy nội dung bài viết, struct trả về { content }"
190
+ - "Hãy lấy số lượng like, share, comment, trả JSON { like, share, comment }"
191
+
192
+ Returns:
193
+ structuredContent (dynamic JSON defined by prompt)
194
+ """
195
+ res = await self.client.call_tool(
196
+ "parseHTMLByPrompt",
197
+ {
198
+ "html": html,
199
+ "prompt": prompt,
200
+ },
201
+ )
202
+ return res.get("structuredContent", {})
@@ -3,7 +3,7 @@ import time
3
3
  import uuid
4
4
  from typing import Any, Dict, List, Optional
5
5
 
6
- import requests
6
+ import httpx
7
7
 
8
8
  DEFAULT_HEADERS = {
9
9
  "Content-Type": "application/json",
@@ -22,22 +22,43 @@ class MCPClient:
22
22
  headers: Optional[Dict[str, str]] = None,
23
23
  timeout: int = 30,
24
24
  retries: int = 1,
25
+ proxies: Optional[Dict[str, str]] = None,
25
26
  ):
26
27
  """
27
28
  base_url: full MCP HTTP endpoint e.g. http://localhost:3000/mcp
28
29
  headers: extra headers (e.g. {"Authorization": "Bearer ..."})
29
30
  timeout: request timeout seconds
30
31
  retries: number of attempts for network errors
32
+ proxies: httpx proxies dict, e.g.
33
+ {
34
+ "http://": "http://127.0.0.1:8080",
35
+ "https://": "http://127.0.0.1:8080"
36
+ }
31
37
  """
32
38
  self.base_url = base_url.rstrip("/")
33
39
  self.headers = DEFAULT_HEADERS.copy()
34
40
  if headers:
35
41
  self.headers.update(headers)
42
+
36
43
  self.timeout = timeout
37
44
  self.retries = max(1, int(retries))
38
- # local cache of sessions -> can be used to reuse session ids
39
45
  self._sessions: Dict[str, Dict[str, Any]] = {}
40
46
 
47
+ # httpx client (sync)
48
+ self._client = httpx.Client(
49
+ base_url=self.base_url,
50
+ headers=self.headers,
51
+ timeout=httpx.Timeout(self.timeout),
52
+ proxies=proxies,
53
+ )
54
+
55
+ def close(self):
56
+ """Close underlying httpx client."""
57
+ self._client.close()
58
+
59
+ # ---------------------------
60
+ # JSON-RPC core
61
+ # ---------------------------
41
62
  def _rpc(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
42
63
  payload = {
43
64
  "jsonrpc": "2.0",
@@ -45,17 +66,14 @@ class MCPClient:
45
66
  "method": method,
46
67
  "params": params,
47
68
  }
48
- last_exc = None
69
+
70
+ last_exc: Optional[Exception] = None
71
+
49
72
  for attempt in range(self.retries):
50
73
  try:
51
- r = requests.post(
52
- self.base_url,
53
- json=payload,
54
- headers=self.headers,
55
- timeout=self.timeout,
56
- )
74
+ r = self._client.post("", json=payload)
57
75
  r.raise_for_status()
58
- except requests.RequestException as e:
76
+ except httpx.HTTPError as e:
59
77
  last_exc = e
60
78
  time.sleep(0.2 * (attempt + 1))
61
79
  continue
@@ -76,59 +94,55 @@ class MCPClient:
76
94
  "data": err.get("data"),
77
95
  }
78
96
  )
97
+
79
98
  return data.get("result", {})
80
99
 
81
- # all retries failed
82
100
  raise MCPError(f"Request failed after {self.retries} attempts: {last_exc}")
83
101
 
84
- # ----- Generic callTool wrapper -----
102
+ # ---------------------------
103
+ # Tool wrappers
104
+ # ---------------------------
85
105
  def call_tool_structured(
86
106
  self, tool: str, arguments: Optional[Dict[str, Any]] = None
87
107
  ) -> Dict[str, Any]:
88
108
  args = {"tool": tool, "arguments": arguments or {}}
89
109
  res = self._rpc("callTool", args)
90
- # return structuredContent when possible
110
+
91
111
  if isinstance(res, dict) and "structuredContent" in res:
92
112
  return res["structuredContent"]
93
- # fallback: maybe top-level result
113
+
94
114
  return res
95
115
 
96
- # ----- Generic callTool wrapper -----
97
116
  def call_tool(
98
117
  self, tool: str, arguments: Optional[Dict[str, Any]] = None
99
118
  ) -> Dict[str, Any]:
100
119
  args = {"tool": tool, "arguments": arguments or {}}
101
- res = self._rpc("callTool", args)
102
- # # return structuredContent when possible
103
- # if isinstance(res, dict) and "structuredContent" in res:
104
- # return res["structuredContent"]
105
- # # fallback: maybe top-level result
106
- return res
120
+ return self._rpc("callTool", args)
107
121
 
108
- # ----- Session helpers -----
122
+ # ---------------------------
123
+ # Session helpers
124
+ # ---------------------------
109
125
  def create_session(self, cdpUrl: str) -> str:
110
126
  result = self.call_tool_structured("createSession", {"cdpUrl": cdpUrl})
111
- # Many MCP servers put sessionId in structuredContent.sessionId or result.sessionId
127
+
112
128
  session_id = None
113
129
  if isinstance(result, dict):
114
130
  session_id = (
115
131
  result.get("sessionId") or result.get("session_id") or result.get("id")
116
132
  )
133
+
117
134
  if not session_id:
118
- # fallback: try raw result text or content
119
135
  raise MCPError("createSession did not return sessionId")
120
- # store meta
136
+
121
137
  self._sessions[session_id] = {"created_at": time.time()}
122
138
  return session_id
123
139
 
124
140
  def close_session(self, session_id: str) -> bool:
125
141
  try:
126
142
  self.call_tool_structured("closeSession", {"sessionId": session_id})
127
- if session_id in self._sessions:
128
- del self._sessions[session_id]
143
+ self._sessions.pop(session_id, None)
129
144
  return True
130
145
  except MCPError:
131
- # still remove local record if present
132
146
  self._sessions.pop(session_id, None)
133
147
  raise
134
148
 
@@ -228,3 +228,29 @@ class MCPTools:
228
228
  "selectorMap",
229
229
  {"sessionId": sessionId, "selector": selector, "args": args or {}},
230
230
  )
231
+
232
+ # ======================================================
233
+ # AI / CONTENT PARSING
234
+ # ======================================================
235
+ @_ensure_client
236
+ def parse_html_by_prompt(self, html: str, prompt: str) -> Dict[str, Any]:
237
+ """
238
+ Parse HTML content using AI with dynamic prompt-defined structure.
239
+
240
+ Args:
241
+ html: Raw HTML string (client-provided)
242
+ prompt: Instruction that defines what to extract and output structure
243
+ Example:
244
+ - "Hãy lấy nội dung bài viết, struct trả về { content }"
245
+ - "Hãy lấy số lượng like, share, comment, trả JSON { like, share, comment }"
246
+
247
+ Returns:
248
+ structuredContent (dynamic JSON defined by prompt)
249
+ """
250
+ return self.client.call_tool(
251
+ "parseHTMLByPrompt",
252
+ {
253
+ "html": html,
254
+ "prompt": prompt,
255
+ },
256
+ ).get("structuredContent", {})
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kl-mcp-client
3
- Version: 1.0.6
4
- Summary: MCP Client
3
+ Version: 1.2.1
4
+ Summary: MCP Client for Python
5
5
  Author-email: Kyle <hngan.it@gmail.com>
6
6
  License: MIT
7
7
  Requires-Python: >=3.8
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = [ "setuptools", "wheel",]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kl-mcp-client"
7
+ version = "1.2.1"
8
+ description = "MCP Client for Python"
9
+ dependencies = [ "requests>=2.32.5", "httpx>=0.28.1",]
10
+ readme = "README.md"
11
+ requires-python = ">=3.8"
12
+ [[project.authors]]
13
+ name = "Kyle"
14
+ email = "hngan.it@gmail.com"
15
+
16
+ [project.license]
17
+ text = "MIT"
@@ -1,18 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "kl-mcp-client"
7
- version = "1.0.6"
8
- description = "MCP Client"
9
- authors = [
10
- { name="Kyle", email="hngan.it@gmail.com" }
11
- ]
12
- dependencies = [
13
- "requests>=2.32.5",
14
- "httpx>=0.28.1"
15
- ]
16
- readme = "README.md"
17
- license = { text = "MIT" }
18
- requires-python = ">=3.8"
File without changes
File without changes