cnhkmcp 1.2.2__py3-none-any.whl → 1.2.4__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.
- cnhkmcp/__init__.py +1 -1
- cnhkmcp/untracked/platform_functions.py +58 -71
- {cnhkmcp-1.2.2.dist-info → cnhkmcp-1.2.4.dist-info}/METADATA +1 -1
- cnhkmcp-1.2.4.dist-info/RECORD +10 -0
- cnhkmcp-1.2.2.dist-info/RECORD +0 -10
- {cnhkmcp-1.2.2.dist-info → cnhkmcp-1.2.4.dist-info}/WHEEL +0 -0
- {cnhkmcp-1.2.2.dist-info → cnhkmcp-1.2.4.dist-info}/entry_points.txt +0 -0
- {cnhkmcp-1.2.2.dist-info → cnhkmcp-1.2.4.dist-info}/licenses/LICENSE +0 -0
- {cnhkmcp-1.2.2.dist-info → cnhkmcp-1.2.4.dist-info}/top_level.txt +0 -0
cnhkmcp/__init__.py
CHANGED
|
@@ -26,6 +26,9 @@ from bs4 import BeautifulSoup
|
|
|
26
26
|
from mcp.server.fastmcp import FastMCP
|
|
27
27
|
from pydantic import BaseModel, Field, EmailStr
|
|
28
28
|
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
|
|
29
32
|
# Configure logging
|
|
30
33
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
31
34
|
logger = logging.getLogger(__name__)
|
|
@@ -601,8 +604,8 @@ class BrainApiClient:
|
|
|
601
604
|
self.log(f"Failed to get user profile: {str(e)}", "ERROR")
|
|
602
605
|
raise
|
|
603
606
|
|
|
604
|
-
async def
|
|
605
|
-
"""Get available
|
|
607
|
+
async def get_documentations(self) -> Dict[str, Any]:
|
|
608
|
+
"""Get available documentations and learning materials."""
|
|
606
609
|
await self.ensure_authenticated()
|
|
607
610
|
|
|
608
611
|
try:
|
|
@@ -610,7 +613,7 @@ class BrainApiClient:
|
|
|
610
613
|
response.raise_for_status()
|
|
611
614
|
return response.json()
|
|
612
615
|
except Exception as e:
|
|
613
|
-
self.log(f"Failed to get
|
|
616
|
+
self.log(f"Failed to get documentations: {str(e)}", "ERROR")
|
|
614
617
|
raise
|
|
615
618
|
|
|
616
619
|
# get_messages_summary function removed as requested
|
|
@@ -1189,8 +1192,8 @@ class BrainApiClient:
|
|
|
1189
1192
|
|
|
1190
1193
|
# generate_alpha_links function removed as requested
|
|
1191
1194
|
|
|
1192
|
-
async def
|
|
1193
|
-
"""Retrieve detailed content of a specific
|
|
1195
|
+
async def get_documentation_page(self, page_id: str) -> Dict[str, Any]:
|
|
1196
|
+
"""Retrieve detailed content of a specific documentation page/article."""
|
|
1194
1197
|
await self.ensure_authenticated()
|
|
1195
1198
|
|
|
1196
1199
|
try:
|
|
@@ -1198,7 +1201,7 @@ class BrainApiClient:
|
|
|
1198
1201
|
response.raise_for_status()
|
|
1199
1202
|
return response.json()
|
|
1200
1203
|
except Exception as e:
|
|
1201
|
-
self.log(f"Failed to get
|
|
1204
|
+
self.log(f"Failed to get documentation page: {str(e)}", "ERROR")
|
|
1202
1205
|
raise
|
|
1203
1206
|
|
|
1204
1207
|
# Badge status function removed as requested
|
|
@@ -1213,39 +1216,44 @@ brain_client = BrainApiClient()
|
|
|
1213
1216
|
CONFIG_FILE = "user_config.json"
|
|
1214
1217
|
|
|
1215
1218
|
def _resolve_config_path(for_write: bool = False) -> str:
|
|
1216
|
-
"""
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
3) Current working directory (read fallback only)
|
|
1219
|
+
"""
|
|
1220
|
+
Resolve the config file path with this priority:
|
|
1221
|
+
1) BRAIN_CONFIG_PATH (file or directory)
|
|
1222
|
+
2) Directory of running script when available, else current working directory
|
|
1223
|
+
3) Current working directory
|
|
1222
1224
|
|
|
1223
|
-
When for_write
|
|
1224
|
-
return that even if it doesn't exist yet.
|
|
1225
|
+
When for_write=True, returns the preferred path even if it doesn't exist yet.
|
|
1225
1226
|
"""
|
|
1227
|
+
# 1) Explicit override via env var
|
|
1228
|
+
env_path = os.environ.get("BRAIN_CONFIG_PATH")
|
|
1229
|
+
if env_path:
|
|
1230
|
+
p = Path(env_path).expanduser()
|
|
1231
|
+
target = p / CONFIG_FILE if p.is_dir() else p
|
|
1232
|
+
# For read, only if it exists; for write, allow regardless
|
|
1233
|
+
if for_write or target.exists():
|
|
1234
|
+
return str(target.resolve())
|
|
1235
|
+
|
|
1236
|
+
# 2) Script/module directory when available, else CWD (works in notebooks)
|
|
1237
|
+
base_dir = Path.cwd()
|
|
1226
1238
|
try:
|
|
1227
|
-
#
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
except Exception as e:
|
|
1246
|
-
# As a last resort, use the bare filename in CWD
|
|
1247
|
-
logger.error(f"Failed to resolve config path, defaulting to CWD: {e}")
|
|
1248
|
-
return os.path.abspath(CONFIG_FILE)
|
|
1239
|
+
# __file__ is not defined in notebooks; this will fail there and keep CWD
|
|
1240
|
+
script_dir = Path(__file__).resolve().parent # type: ignore[name-defined]
|
|
1241
|
+
base_dir = script_dir
|
|
1242
|
+
except Exception:
|
|
1243
|
+
# Fall back to current working directory for notebooks/REPL
|
|
1244
|
+
pass
|
|
1245
|
+
|
|
1246
|
+
module_path = base_dir / CONFIG_FILE
|
|
1247
|
+
if not for_write and module_path.exists():
|
|
1248
|
+
return str(module_path.resolve())
|
|
1249
|
+
|
|
1250
|
+
# 3) Fallback to CWD for backward compatibility
|
|
1251
|
+
cwd_path = Path.cwd() / CONFIG_FILE
|
|
1252
|
+
if not for_write and cwd_path.exists():
|
|
1253
|
+
return str(cwd_path.resolve())
|
|
1254
|
+
|
|
1255
|
+
# For writes (or when nothing exists), prefer the module/base directory
|
|
1256
|
+
return str(module_path.resolve())
|
|
1249
1257
|
|
|
1250
1258
|
def load_config() -> Dict[str, Any]:
|
|
1251
1259
|
"""Load configuration from file with robust path resolution.
|
|
@@ -1262,19 +1270,6 @@ def load_config() -> Dict[str, Any]:
|
|
|
1262
1270
|
logger.error(f"Failed to load config from '{path}': {e}")
|
|
1263
1271
|
return {}
|
|
1264
1272
|
|
|
1265
|
-
def load_brain_credentials() -> Optional[tuple]:
|
|
1266
|
-
"""Load credentials from .brain_credentials file in home directory."""
|
|
1267
|
-
try:
|
|
1268
|
-
from os.path import expanduser
|
|
1269
|
-
credentials_file = expanduser('~/.brain_credentials')
|
|
1270
|
-
if os.path.exists(credentials_file):
|
|
1271
|
-
with open(credentials_file, 'r') as f:
|
|
1272
|
-
credentials = json.load(f)
|
|
1273
|
-
if isinstance(credentials, list) and len(credentials) == 2:
|
|
1274
|
-
return tuple(credentials)
|
|
1275
|
-
except Exception as e:
|
|
1276
|
-
logger.error(f"Failed to load .brain_credentials: {e}")
|
|
1277
|
-
return None
|
|
1278
1273
|
|
|
1279
1274
|
def save_config(config: Dict[str, Any]):
|
|
1280
1275
|
"""Save configuration to file using the resolved config path.
|
|
@@ -1293,7 +1288,7 @@ def save_config(config: Dict[str, Any]):
|
|
|
1293
1288
|
# MCP Tools
|
|
1294
1289
|
|
|
1295
1290
|
@mcp.tool()
|
|
1296
|
-
async def authenticate(email: str = "", password: str = "") -> Dict[str, Any]:
|
|
1291
|
+
async def authenticate(email: Optional[str] = "", password: Optional[str] = "") -> Dict[str, Any]:
|
|
1297
1292
|
"""
|
|
1298
1293
|
🔐 Authenticate with WorldQuant BRAIN platform.
|
|
1299
1294
|
|
|
@@ -1307,20 +1302,12 @@ async def authenticate(email: str = "", password: str = "") -> Dict[str, Any]:
|
|
|
1307
1302
|
Authentication result with user info and permissions
|
|
1308
1303
|
"""
|
|
1309
1304
|
try:
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
# If still no credentials, try config file
|
|
1317
|
-
if not email or not password:
|
|
1318
|
-
config = load_config()
|
|
1319
|
-
if 'credentials' in config:
|
|
1320
|
-
if not email:
|
|
1321
|
-
email = config['credentials'].get('email', '')
|
|
1322
|
-
if not password:
|
|
1323
|
-
password = config['credentials'].get('password', '')
|
|
1305
|
+
config = load_config()
|
|
1306
|
+
if 'credentials' in config:
|
|
1307
|
+
if not email:
|
|
1308
|
+
email = config['credentials'].get('email', '')
|
|
1309
|
+
if not password:
|
|
1310
|
+
password = config['credentials'].get('password', '')
|
|
1324
1311
|
|
|
1325
1312
|
if not email or not password:
|
|
1326
1313
|
return {"error": "Email and password required. Either provide them as arguments, configure them in user_config.json, or create a .brain_credentials file in your home directory with format: [\"email\", \"password\"]"}
|
|
@@ -1756,15 +1743,15 @@ async def get_user_profile(user_id: str = "self") -> Dict[str, Any]:
|
|
|
1756
1743
|
return {"error": str(e)}
|
|
1757
1744
|
|
|
1758
1745
|
@mcp.tool()
|
|
1759
|
-
async def
|
|
1746
|
+
async def get_documentations() -> Dict[str, Any]:
|
|
1760
1747
|
"""
|
|
1761
|
-
📚 Get available
|
|
1748
|
+
📚 Get available documentations and learning materials.
|
|
1762
1749
|
|
|
1763
1750
|
Returns:
|
|
1764
|
-
List of
|
|
1751
|
+
List of documentations
|
|
1765
1752
|
"""
|
|
1766
1753
|
try:
|
|
1767
|
-
return await brain_client.
|
|
1754
|
+
return await brain_client.get_documentations()
|
|
1768
1755
|
except Exception as e:
|
|
1769
1756
|
return {"error": str(e)}
|
|
1770
1757
|
|
|
@@ -2029,10 +2016,10 @@ async def expand_nested_data(data: List[Dict[str, Any]], preserve_original: bool
|
|
|
2029
2016
|
# generate_alpha_links MCP tool removed as requested
|
|
2030
2017
|
|
|
2031
2018
|
@mcp.tool()
|
|
2032
|
-
async def
|
|
2033
|
-
"""Retrieve detailed content of a specific
|
|
2019
|
+
async def get_documentation_page(page_id: str) -> Dict[str, Any]:
|
|
2020
|
+
"""Retrieve detailed content of a specific documentation page/article."""
|
|
2034
2021
|
try:
|
|
2035
|
-
return await brain_client.
|
|
2022
|
+
return await brain_client.get_documentation_page(page_id)
|
|
2036
2023
|
except Exception as e:
|
|
2037
2024
|
return {"error": str(e)}
|
|
2038
2025
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
cnhkmcp/__init__.py,sha256=-laBHbRG7AtBQp4pu6I9nSFtNBoBp_p35pQzNcf2Jkw,2758
|
|
2
|
+
cnhkmcp/untracked/forum_functions.py,sha256=78wzvN_UYWwbWU40q8_FJNSFPJnND6W9ZRey6MSSiEk,45516
|
|
3
|
+
cnhkmcp/untracked/platform_functions.py,sha256=o0l1MNcUUmvCRgjhxbn0QaxdZSLyV_C1DI-2DaXx9Eg,81739
|
|
4
|
+
cnhkmcp/untracked/user_config.json,sha256=_INn1X1qIsITrmEno-BRlQOAGm9wnNCw-6B333DEvnk,695
|
|
5
|
+
cnhkmcp-1.2.4.dist-info/licenses/LICENSE,sha256=QLxO2eNMnJQEdI_R1UV2AOD-IvuA8zVrkHWA4D9gtoc,1081
|
|
6
|
+
cnhkmcp-1.2.4.dist-info/METADATA,sha256=a86cCJaC48vkhIHafwnyDZYliEfK6Ht6qGZdO7qCsy8,5171
|
|
7
|
+
cnhkmcp-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
cnhkmcp-1.2.4.dist-info/entry_points.txt,sha256=lTQieVyIvjhSMK4fT-XwnccY-JBC1H4vVQ3V9dDM-Pc,70
|
|
9
|
+
cnhkmcp-1.2.4.dist-info/top_level.txt,sha256=x--ibUcSgOS9Z_RWK2Qc-vfs7DaXQN-WMaaxEETJ1Bw,8
|
|
10
|
+
cnhkmcp-1.2.4.dist-info/RECORD,,
|
cnhkmcp-1.2.2.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
cnhkmcp/__init__.py,sha256=jLSpv4B0gds0czwoFi1w5-qZBViB9f8yUZN4_9U1yf8,2758
|
|
2
|
-
cnhkmcp/untracked/forum_functions.py,sha256=78wzvN_UYWwbWU40q8_FJNSFPJnND6W9ZRey6MSSiEk,45516
|
|
3
|
-
cnhkmcp/untracked/platform_functions.py,sha256=7Vnul6prlHwCNB0L0X_sLwR_-C53y1sKDhAKF6KOE1A,82452
|
|
4
|
-
cnhkmcp/untracked/user_config.json,sha256=_INn1X1qIsITrmEno-BRlQOAGm9wnNCw-6B333DEvnk,695
|
|
5
|
-
cnhkmcp-1.2.2.dist-info/licenses/LICENSE,sha256=QLxO2eNMnJQEdI_R1UV2AOD-IvuA8zVrkHWA4D9gtoc,1081
|
|
6
|
-
cnhkmcp-1.2.2.dist-info/METADATA,sha256=xAuZdgGb0hWe_FmVuTT0JTVwjb3tstKBcyerAlr8rMI,5171
|
|
7
|
-
cnhkmcp-1.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
cnhkmcp-1.2.2.dist-info/entry_points.txt,sha256=lTQieVyIvjhSMK4fT-XwnccY-JBC1H4vVQ3V9dDM-Pc,70
|
|
9
|
-
cnhkmcp-1.2.2.dist-info/top_level.txt,sha256=x--ibUcSgOS9Z_RWK2Qc-vfs7DaXQN-WMaaxEETJ1Bw,8
|
|
10
|
-
cnhkmcp-1.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|