cnhkmcp 1.1.6__tar.gz → 1.1.8__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: cnhkmcp
3
- Version: 1.1.6
3
+ Version: 1.1.8
4
4
  Summary: A comprehensive Model Context Protocol (MCP) server for quantitative trading platform integration
5
5
  Home-page: https://github.com/cnhk/cnhkmcp
6
6
  Author: CNHK
@@ -50,7 +50,7 @@ from .untracked.forum_functions import (
50
50
  read_full_forum_post
51
51
  )
52
52
 
53
- __version__ = "1.1.6"
53
+ __version__ = "1.1.8"
54
54
  __author__ = "CNHK"
55
55
  __email__ = "cnhk@example.com"
56
56
 
@@ -81,8 +81,8 @@ class BrainApiClient:
81
81
  print(f"[{level}] {message}", file=sys.stderr)
82
82
 
83
83
  async def authenticate(self, email: str, password: str) -> Dict[str, Any]:
84
- """Authenticate with WorldQuant BRAIN platform using Basic Authentication."""
85
- self.log("🔐 Starting Basic Authentication process...", "INFO")
84
+ """Authenticate with WorldQuant BRAIN platform with biometric support."""
85
+ self.log("🔐 Starting Authentication process...", "INFO")
86
86
 
87
87
  try:
88
88
  # Store credentials for potential re-authentication
@@ -106,7 +106,7 @@ class BrainApiClient:
106
106
 
107
107
  # Check for successful authentication (status code 201)
108
108
  if response.status_code == 201:
109
- self.log("✅ Basic Authentication successful", "SUCCESS")
109
+ self.log("✅ Authentication successful", "SUCCESS")
110
110
 
111
111
  # Check if JWT token was automatically stored by session
112
112
  jwt_token = self.session.cookies.get('t')
@@ -120,10 +120,26 @@ class BrainApiClient:
120
120
  'user': {'email': email},
121
121
  'status': 'authenticated',
122
122
  'permissions': ['read', 'write'],
123
- 'message': 'Basic Authentication successful',
123
+ 'message': 'Authentication successful',
124
124
  'status_code': response.status_code,
125
125
  'has_jwt': jwt_token is not None
126
126
  }
127
+
128
+ # Check if biometric authentication is required (401 with persona)
129
+ elif response.status_code == 401:
130
+ www_auth = response.headers.get("WWW-Authenticate")
131
+ location = response.headers.get("Location")
132
+
133
+ if www_auth == "persona" and location:
134
+ self.log("🔴 Biometric authentication required", "INFO")
135
+
136
+ # Handle biometric authentication
137
+ from urllib.parse import urljoin
138
+ biometric_url = urljoin(response.url, location)
139
+
140
+ return await self._handle_biometric_auth(biometric_url, email)
141
+ else:
142
+ raise Exception("Incorrect email or password")
127
143
  else:
128
144
  raise Exception(f"Authentication failed with status code: {response.status_code}")
129
145
 
@@ -134,6 +150,97 @@ class BrainApiClient:
134
150
  self.log(f"❌ Authentication failed: {str(e)}", "ERROR")
135
151
  raise
136
152
 
153
+ async def _handle_biometric_auth(self, biometric_url: str, email: str) -> Dict[str, Any]:
154
+ """Handle biometric authentication using browser automation."""
155
+ self.log("🌐 Starting biometric authentication...", "INFO")
156
+
157
+ try:
158
+ # Import selenium for browser automation
159
+ from selenium import webdriver
160
+ from selenium.webdriver.chrome.options import Options
161
+ import time
162
+
163
+ # Setup Chrome options
164
+ options = Options()
165
+ options.add_argument('--no-sandbox')
166
+ options.add_argument('--disable-dev-shm-usage')
167
+
168
+ driver = None
169
+ try:
170
+ # Open browser with timeout
171
+ driver = webdriver.Chrome(options=options)
172
+ # Set a short timeout so it doesn't wait forever
173
+ driver.set_page_load_timeout(80) # Only wait 5 seconds
174
+
175
+ self.log("🌐 Opening browser for biometric authentication...", "INFO")
176
+
177
+ # Try to open the URL but handle timeout
178
+ try:
179
+ driver.get(biometric_url)
180
+ self.log("✅ Browser page loaded successfully", "SUCCESS")
181
+ except Exception as timeout_error:
182
+ self.log(f"⚠️ Page load timeout (expected): {str(timeout_error)[:50]}...", "WARNING")
183
+ self.log("✅ Browser window is open for biometric authentication", "INFO")
184
+
185
+ # Print instructions
186
+ print("\n" + "="*60, file=sys.stderr)
187
+ print("🔒 BIOMETRIC AUTHENTICATION REQUIRED", file=sys.stderr)
188
+ print("="*60, file=sys.stderr)
189
+ print("🌐 Browser window is open with biometric authentication page", file=sys.stderr)
190
+ print("🔧 Complete the biometric authentication in the browser", file=sys.stderr)
191
+ print("⏳ The system will automatically check when you're done...", file=sys.stderr)
192
+ print("="*60, file=sys.stderr)
193
+
194
+ # Keep checking until authentication is complete
195
+ max_attempts = 60 # 5 minutes maximum (60 * 5 seconds)
196
+ attempt = 0
197
+
198
+ while attempt < max_attempts:
199
+ time.sleep(5) # Check every 5 seconds
200
+ attempt += 1
201
+
202
+ # Check if authentication completed
203
+ check_response = self.session.post(biometric_url)
204
+ self.log(f"🔄 Checking authentication status (attempt {attempt}/{max_attempts}): {check_response.status_code}", "INFO")
205
+
206
+ if check_response.status_code == 201:
207
+ self.log("✅ Biometric authentication successful!", "SUCCESS")
208
+
209
+ # Close browser
210
+ driver.quit()
211
+
212
+ # Check JWT token
213
+ jwt_token = self.session.cookies.get('t')
214
+ if jwt_token:
215
+ self.log("✅ JWT token received", "SUCCESS")
216
+
217
+ # Return success response
218
+ return {
219
+ 'user': {'email': email},
220
+ 'status': 'authenticated',
221
+ 'permissions': ['read', 'write'],
222
+ 'message': 'Biometric authentication successful',
223
+ 'status_code': check_response.status_code,
224
+ 'has_jwt': jwt_token is not None
225
+ }
226
+
227
+ # If we get here, authentication timed out
228
+ if driver:
229
+ driver.quit()
230
+ raise Exception("Biometric authentication timed out")
231
+
232
+ except Exception as driver_error:
233
+ if driver:
234
+ try:
235
+ driver.quit()
236
+ except:
237
+ pass
238
+ raise Exception(f"Browser automation error: {driver_error}")
239
+
240
+ except Exception as e:
241
+ self.log(f"❌ Biometric authentication failed: {str(e)}", "ERROR")
242
+ raise
243
+
137
244
  async def is_authenticated(self) -> bool:
138
245
  """Check if currently authenticated using JWT token."""
139
246
  try:
@@ -322,13 +429,12 @@ class BrainApiClient:
322
429
  self.log(f"Failed to get datafields: {str(e)}", "ERROR")
323
430
  raise
324
431
 
325
- async def get_alpha_pnl(self, alpha_id: str, pnl_type: str = "pnl") -> Dict[str, Any]:
432
+ async def get_alpha_pnl(self, alpha_id: str) -> Dict[str, Any]:
326
433
  """Get PnL data for an alpha."""
327
434
  await self.ensure_authenticated()
328
435
 
329
436
  try:
330
- params = {'type': pnl_type}
331
- response = self.session.get(f"{self.base_url}/alphas/{alpha_id}/recordsets/{pnl_type}", params=params)
437
+ response = self.session.get(f"{self.base_url}/alphas/{alpha_id}/recordsets/pnl")
332
438
  response.raise_for_status()
333
439
  return response.json()
334
440
  except Exception as e:
@@ -820,8 +926,7 @@ class BrainApiClient:
820
926
  self.log(f"Failed to get user activities: {str(e)}", "ERROR")
821
927
  raise
822
928
 
823
- async def get_pyramid_multipliers(self, region: Optional[str] = None, delay: Optional[int] = None,
824
- category: Optional[str] = None) -> Dict[str, Any]:
929
+ async def get_pyramid_multipliers(self) -> Dict[str, Any]:
825
930
  """Get current pyramid multipliers showing BRAIN's encouragement levels."""
826
931
  await self.ensure_authenticated()
827
932
 
@@ -1352,19 +1457,18 @@ async def get_datafields(
1352
1457
  return {"error": str(e)}
1353
1458
 
1354
1459
  @mcp.tool()
1355
- async def get_alpha_pnl(alpha_id: str, pnl_type: str = "pnl") -> Dict[str, Any]:
1460
+ async def get_alpha_pnl(alpha_id: str) -> Dict[str, Any]:
1356
1461
  """
1357
1462
  📈 Get PnL (Profit and Loss) data for an alpha.
1358
1463
 
1359
1464
  Args:
1360
1465
  alpha_id: The ID of the alpha
1361
- pnl_type: Type of PnL data ("pnl" for cumulative, "daily-pnl" for daily)
1362
1466
 
1363
1467
  Returns:
1364
1468
  PnL data for the alpha
1365
1469
  """
1366
1470
  try:
1367
- return await brain_client.get_alpha_pnl(alpha_id, pnl_type)
1471
+ return await brain_client.get_alpha_pnl(alpha_id)
1368
1472
  except Exception as e:
1369
1473
  return {"error": str(e)}
1370
1474
 
@@ -1376,8 +1480,8 @@ async def get_user_alphas(stage: str = "IS", limit: int = 100, offset: int = 0)
1376
1480
  Args:
1377
1481
  stage: Alpha stage ("IS" for in-sample, "OS" for out-of-sample)
1378
1482
  limit: Maximum number of alphas to return
1379
- offset: Offset for pagination
1380
-
1483
+ offset: Offset for pagination, if the desired total number is larger than 100. then it is useful, every page, the load max is 100 alphas.
1484
+
1381
1485
  Returns:
1382
1486
  User's alphas
1383
1487
  """
@@ -1745,11 +1849,10 @@ async def get_user_activities(user_id: str, grouping: Optional[str] = None) -> D
1745
1849
  return {"error": str(e)}
1746
1850
 
1747
1851
  @mcp.tool()
1748
- async def get_pyramid_multipliers(region: Optional[str] = None, delay: Optional[int] = None,
1749
- category: Optional[str] = None) -> Dict[str, Any]:
1852
+ async def get_pyramid_multipliers() -> Dict[str, Any]:
1750
1853
  """Get current pyramid multipliers showing BRAIN's encouragement levels."""
1751
1854
  try:
1752
- return await brain_client.get_pyramid_multipliers(region, delay, category)
1855
+ return await brain_client.get_pyramid_multipliers()
1753
1856
  except Exception as e:
1754
1857
  return {"error": str(e)}
1755
1858
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cnhkmcp
3
- Version: 1.1.6
3
+ Version: 1.1.8
4
4
  Summary: A comprehensive Model Context Protocol (MCP) server for quantitative trading platform integration
5
5
  Home-page: https://github.com/cnhk/cnhkmcp
6
6
  Author: CNHK
@@ -13,7 +13,7 @@ def read_requirements():
13
13
 
14
14
  setup(
15
15
  name="cnhkmcp",
16
- version="1.1.6",
16
+ version="1.1.8",
17
17
  author="CNHK",
18
18
  author_email="cnhk@example.com",
19
19
  description="A comprehensive Model Context Protocol (MCP) server for quantitative trading platform integration",
File without changes
File without changes
File without changes
File without changes
File without changes