cnhkmcp 1.2.1__tar.gz → 1.2.3__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.
- {cnhkmcp-1.2.1/cnhkmcp.egg-info → cnhkmcp-1.2.3}/PKG-INFO +1 -1
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp/__init__.py +1 -1
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp/untracked/platform_functions.py +82 -41
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3/cnhkmcp.egg-info}/PKG-INFO +1 -1
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/setup.py +1 -1
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/LICENSE +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/MANIFEST.in +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/README.md +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp/untracked/forum_functions.py +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp/untracked/user_config.json +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp.egg-info/SOURCES.txt +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp.egg-info/dependency_links.txt +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp.egg-info/entry_points.txt +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp.egg-info/not-zip-safe +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp.egg-info/requires.txt +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/cnhkmcp.egg-info/top_level.txt +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/requirements.txt +0 -0
- {cnhkmcp-1.2.1 → cnhkmcp-1.2.3}/setup.cfg +0 -0
|
@@ -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__)
|
|
@@ -389,7 +392,7 @@ class BrainApiClient:
|
|
|
389
392
|
response = self.session.get(f"{self.base_url}/data-sets", params=params)
|
|
390
393
|
response.raise_for_status()
|
|
391
394
|
response = response.json()
|
|
392
|
-
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using
|
|
395
|
+
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using get_platform_setting_options tool to got correct parameter"
|
|
393
396
|
return response
|
|
394
397
|
except Exception as e:
|
|
395
398
|
self.log(f"Failed to get datasets: {str(e)}", "ERROR")
|
|
@@ -423,7 +426,7 @@ class BrainApiClient:
|
|
|
423
426
|
response = self.session.get(f"{self.base_url}/data-fields", params=params)
|
|
424
427
|
response.raise_for_status()
|
|
425
428
|
response = response.json()
|
|
426
|
-
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using
|
|
429
|
+
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using get_platform_setting_options tool to got correct parameter"
|
|
427
430
|
return response
|
|
428
431
|
except Exception as e:
|
|
429
432
|
self.log(f"Failed to get datafields: {str(e)}", "ERROR")
|
|
@@ -1062,7 +1065,7 @@ class BrainApiClient:
|
|
|
1062
1065
|
self.log(f"Failed to get competition agreement: {str(e)}", "ERROR")
|
|
1063
1066
|
raise
|
|
1064
1067
|
|
|
1065
|
-
async def
|
|
1068
|
+
async def get_platform_setting_options(self) -> Dict[str, Any]:
|
|
1066
1069
|
"""Get available instrument types, regions, delays, and universes."""
|
|
1067
1070
|
await self.ensure_authenticated()
|
|
1068
1071
|
|
|
@@ -1212,42 +1215,80 @@ brain_client = BrainApiClient()
|
|
|
1212
1215
|
# Configuration management
|
|
1213
1216
|
CONFIG_FILE = "user_config.json"
|
|
1214
1217
|
|
|
1218
|
+
def _resolve_config_path(for_write: bool = False) -> str:
|
|
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
|
|
1224
|
+
|
|
1225
|
+
When for_write=True, returns the preferred path even if it doesn't exist yet.
|
|
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()
|
|
1238
|
+
try:
|
|
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())
|
|
1257
|
+
|
|
1215
1258
|
def load_config() -> Dict[str, Any]:
|
|
1216
|
-
"""Load configuration from file.
|
|
1217
|
-
|
|
1259
|
+
"""Load configuration from file with robust path resolution.
|
|
1260
|
+
|
|
1261
|
+
Looks for the config in this order: BRAIN_CONFIG_PATH -> module directory -> CWD.
|
|
1262
|
+
Returns an empty dict when not found or on error.
|
|
1263
|
+
"""
|
|
1264
|
+
path = _resolve_config_path(for_write=False)
|
|
1265
|
+
if os.path.exists(path):
|
|
1218
1266
|
try:
|
|
1219
|
-
with open(
|
|
1267
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
1220
1268
|
return json.load(f)
|
|
1221
1269
|
except Exception as e:
|
|
1222
|
-
logger.error(f"Failed to load config: {e}")
|
|
1270
|
+
logger.error(f"Failed to load config from '{path}': {e}")
|
|
1223
1271
|
return {}
|
|
1224
1272
|
|
|
1225
|
-
def load_brain_credentials() -> Optional[tuple]:
|
|
1226
|
-
"""Load credentials from .brain_credentials file in home directory."""
|
|
1227
|
-
try:
|
|
1228
|
-
from os.path import expanduser
|
|
1229
|
-
credentials_file = expanduser('~/.brain_credentials')
|
|
1230
|
-
if os.path.exists(credentials_file):
|
|
1231
|
-
with open(credentials_file, 'r') as f:
|
|
1232
|
-
credentials = json.load(f)
|
|
1233
|
-
if isinstance(credentials, list) and len(credentials) == 2:
|
|
1234
|
-
return tuple(credentials)
|
|
1235
|
-
except Exception as e:
|
|
1236
|
-
logger.error(f"Failed to load .brain_credentials: {e}")
|
|
1237
|
-
return None
|
|
1238
1273
|
|
|
1239
1274
|
def save_config(config: Dict[str, Any]):
|
|
1240
|
-
"""Save configuration to file.
|
|
1275
|
+
"""Save configuration to file using the resolved config path.
|
|
1276
|
+
|
|
1277
|
+
Uses BRAIN_CONFIG_PATH if set; otherwise writes next to this module.
|
|
1278
|
+
Ensures the target directory exists.
|
|
1279
|
+
"""
|
|
1241
1280
|
try:
|
|
1242
|
-
|
|
1243
|
-
|
|
1281
|
+
path = _resolve_config_path(for_write=True)
|
|
1282
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
1283
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
1284
|
+
json.dump(config, f, indent=2, ensure_ascii=False)
|
|
1244
1285
|
except Exception as e:
|
|
1245
1286
|
logger.error(f"Failed to save config: {e}")
|
|
1246
1287
|
|
|
1247
1288
|
# MCP Tools
|
|
1248
1289
|
|
|
1249
1290
|
@mcp.tool()
|
|
1250
|
-
async def authenticate(email: str = "", password: str = "") -> Dict[str, Any]:
|
|
1291
|
+
async def authenticate(email: Optional[str] = "", password: Optional[str] = "") -> Dict[str, Any]:
|
|
1251
1292
|
"""
|
|
1252
1293
|
🔐 Authenticate with WorldQuant BRAIN platform.
|
|
1253
1294
|
|
|
@@ -1261,20 +1302,12 @@ async def authenticate(email: str = "", password: str = "") -> Dict[str, Any]:
|
|
|
1261
1302
|
Authentication result with user info and permissions
|
|
1262
1303
|
"""
|
|
1263
1304
|
try:
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
# If still no credentials, try config file
|
|
1271
|
-
if not email or not password:
|
|
1272
|
-
config = load_config()
|
|
1273
|
-
if 'credentials' in config:
|
|
1274
|
-
if not email:
|
|
1275
|
-
email = config['credentials'].get('email', '')
|
|
1276
|
-
if not password:
|
|
1277
|
-
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', '')
|
|
1278
1311
|
|
|
1279
1312
|
if not email or not password:
|
|
1280
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\"]"}
|
|
@@ -1946,10 +1979,18 @@ async def get_competition_agreement(competition_id: str) -> Dict[str, Any]:
|
|
|
1946
1979
|
return {"error": str(e)}
|
|
1947
1980
|
|
|
1948
1981
|
@mcp.tool()
|
|
1949
|
-
async def
|
|
1950
|
-
"""
|
|
1982
|
+
async def get_platform_setting_options() -> Dict[str, Any]:
|
|
1983
|
+
"""Discover valid simulation setting options (instrument types, regions, delays, universes, neutralization).
|
|
1984
|
+
|
|
1985
|
+
Use this when a simulation request might contain an invalid/mismatched setting. If an AI or user supplies
|
|
1986
|
+
incorrect parameters (e.g., wrong region for an instrument type), call this tool to retrieve the authoritative
|
|
1987
|
+
option sets and correct the inputs before proceeding.
|
|
1988
|
+
|
|
1989
|
+
Returns:
|
|
1990
|
+
A structured list of valid combinations and choice lists to validate or fix simulation settings.
|
|
1991
|
+
"""
|
|
1951
1992
|
try:
|
|
1952
|
-
return await brain_client.
|
|
1993
|
+
return await brain_client.get_platform_setting_options()
|
|
1953
1994
|
except Exception as e:
|
|
1954
1995
|
return {"error": str(e)}
|
|
1955
1996
|
|
|
@@ -13,7 +13,7 @@ def read_requirements():
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="cnhkmcp",
|
|
16
|
-
version="1.2.
|
|
16
|
+
version="1.2.3",
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|