cnhkmcp 1.4.0__py3-none-any.whl → 1.4.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.
cnhkmcp/__init__.py CHANGED
@@ -50,7 +50,7 @@ from .untracked.forum_functions import (
50
50
  read_full_forum_post
51
51
  )
52
52
 
53
- __version__ = "1.4.0"
53
+ __version__ = "1.4.2"
54
54
  __author__ = "CNHK"
55
55
  __email__ = "cnhk@example.com"
56
56
 
@@ -1,9 +1,17 @@
1
1
  #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
2
3
  """
3
4
  WorldQuant BRAIN MCP Server - Python Version
4
5
  A comprehensive Model Context Protocol (MCP) server for WorldQuant BRAIN platform integration.
5
6
  """
6
7
 
8
+ # Ensure proper encoding handling for Windows
9
+ import sys
10
+ import os
11
+
12
+ # Note: We'll handle encoding issues in individual functions rather than
13
+ # overriding system streams to avoid conflicts with MCP server
14
+
7
15
  import json
8
16
  import time
9
17
  import asyncio
@@ -11,8 +19,6 @@ import logging
11
19
  from typing import Dict, List, Optional, Any, Union
12
20
  from dataclasses import dataclass, asdict
13
21
  from datetime import datetime, timedelta
14
- import os
15
- import sys
16
22
  import math
17
23
  from time import sleep
18
24
 
@@ -82,7 +88,20 @@ class BrainApiClient:
82
88
 
83
89
  def log(self, message: str, level: str = "INFO"):
84
90
  """Log messages to stderr to avoid MCP protocol interference."""
85
- print(f"[{level}] {message}", file=sys.stderr)
91
+ try:
92
+ # Try to print with original message first
93
+ print(f"[{level}] {message}", file=sys.stderr)
94
+ except UnicodeEncodeError:
95
+ # Fallback: remove problematic characters and try again
96
+ try:
97
+ safe_message = message.encode('ascii', 'ignore').decode('ascii')
98
+ print(f"[{level}] {safe_message}", file=sys.stderr)
99
+ except Exception:
100
+ # Final fallback: just print the level and a safe message
101
+ print(f"[{level}] Log message", file=sys.stderr)
102
+ except Exception:
103
+ # Final fallback: just print the level and a safe message
104
+ print(f"[{level}] Log message", file=sys.stderr)
86
105
 
87
106
  async def authenticate(self, email: str, password: str) -> Dict[str, Any]:
88
107
  """Authenticate with WorldQuant BRAIN platform with biometric support."""
@@ -716,68 +735,75 @@ class BrainApiClient:
716
735
 
717
736
  from typing import Tuple
718
737
  def process_description(desc: str, message_id: str) -> Tuple[str, List[str]]:
719
- if not desc or image_handling == "keep":
720
- return desc, []
721
- attachments: List[str] = []
722
- # Regex to capture full <img ...> tag with data URI
723
- img_tag_pattern = re.compile(r"<img[^>]+src=\"(data:image/[^\"]+)\"[^>]*>", re.IGNORECASE)
724
- # Iterate over unique matches to avoid double work
725
- matches = list(img_tag_pattern.finditer(desc))
726
- if not matches:
727
- # Additional heuristic: very long base64-looking token inside quotes followed by </img>
728
- # (legacy format noted by user sample). Replace with placeholder.
729
- heuristic_pattern = re.compile(r"([A-Za-z0-9+/]{500,}={0,2})\"\s*</img>")
730
- if image_handling != "keep" and heuristic_pattern.search(desc):
731
- placeholder = "[Embedded image removed - large base64 sequence truncated]"
732
- return heuristic_pattern.sub(placeholder + "</img>", desc), []
733
- return desc, []
734
-
735
- # Ensure save directory exists only if we will store something
736
- if image_handling == "placeholder" and not save_dir.exists():
737
- try:
738
- save_dir.mkdir(parents=True, exist_ok=True)
739
- except Exception as e:
740
- self.log(f"Could not create image save directory: {e}", "WARNING")
741
-
742
- new_desc = desc
743
- for idx, match in enumerate(matches, start=1):
744
- data_uri = match.group(1) # data:image/...;base64,XXXX
745
- if not data_uri.lower().startswith("data:image"):
746
- continue
747
- # Split header and base64 payload
748
- if "," not in data_uri:
749
- continue
750
- header, b64_data = data_uri.split(",", 1)
751
- mime_part = header.split(";")[0] # data:image/png
752
- ext = "png"
753
- if "/" in mime_part:
754
- ext = mime_part.split("/")[1]
755
- safe_ext = (ext or "img").split("?")[0]
756
- placeholder_text = "[Embedded image]"
757
- if image_handling == "ignore":
758
- replacement = f"[Image removed: {safe_ext}]"
759
- elif image_handling == "placeholder":
760
- # Try decode & save
761
- file_name = f"{message_id}_{idx}.{safe_ext}"
762
- file_path = save_dir / file_name
738
+ try:
739
+ if not desc or image_handling == "keep":
740
+ return desc, []
741
+ attachments: List[str] = []
742
+ # Regex to capture full <img ...> tag with data URI
743
+ img_tag_pattern = re.compile(r"<img[^>]+src=\"(data:image/[^\"]+)\"[^>]*>", re.IGNORECASE)
744
+ # Iterate over unique matches to avoid double work
745
+ matches = list(img_tag_pattern.finditer(desc))
746
+ if not matches:
747
+ # Additional heuristic: very long base64-looking token inside quotes followed by </img>
748
+ # (legacy format noted by user sample). Replace with placeholder.
749
+ heuristic_pattern = re.compile(r"([A-Za-z0-9+/]{500,}={0,2})\"\s*</img>")
750
+ if image_handling != "keep" and heuristic_pattern.search(desc):
751
+ placeholder = "[Embedded image removed - large base64 sequence truncated]"
752
+ return heuristic_pattern.sub(placeholder + "</img>", desc), []
753
+ return desc, []
754
+
755
+ # Ensure save directory exists only if we will store something
756
+ if image_handling == "placeholder" and not save_dir.exists():
763
757
  try:
764
- # Guard extremely large strings (>5MB ~ 6.7M base64 chars) to avoid memory blow
765
- if len(b64_data) > 7_000_000:
766
- raise ValueError("Image too large to decode safely")
767
- with open(file_path, "wb") as f:
768
- f.write(base64.b64decode(b64_data))
769
- attachments.append(str(file_path))
770
- replacement = f"[Image extracted -> {file_path}]"
758
+ save_dir.mkdir(parents=True, exist_ok=True)
771
759
  except Exception as e:
772
- self.log(f"Failed to decode embedded image in message {message_id}: {e}", "WARNING")
773
- replacement = "[Image extraction failed - content omitted]"
774
- else: # keep
775
- replacement = placeholder_text # shouldn't be used since early return, but safe
776
- # Replace only the matched tag (not global) – use re.sub with count=1 on substring slice
777
- # Safer to operate on new_desc using the exact matched string
778
- original_tag = match.group(0)
779
- new_desc = new_desc.replace(original_tag, replacement, 1)
780
- return new_desc, attachments
760
+ self.log(f"Could not create image save directory: {e}", "WARNING")
761
+
762
+ new_desc = desc
763
+ for idx, match in enumerate(matches, start=1):
764
+ data_uri = match.group(1) # data:image/...;base64,XXXX
765
+ if not data_uri.lower().startswith("data:image"):
766
+ continue
767
+ # Split header and base64 payload
768
+ if "," not in data_uri:
769
+ continue
770
+ header, b64_data = data_uri.split(",", 1)
771
+ mime_part = header.split(";")[0] # data:image/png
772
+ ext = "png"
773
+ if "/" in mime_part:
774
+ ext = mime_part.split("/")[1]
775
+ safe_ext = (ext or "img").split("?")[0]
776
+ placeholder_text = "[Embedded image]"
777
+ if image_handling == "ignore":
778
+ replacement = f"[Image removed: {safe_ext}]"
779
+ elif image_handling == "placeholder":
780
+ # Try decode & save
781
+ file_name = f"{message_id}_{idx}.{safe_ext}"
782
+ file_path = save_dir / file_name
783
+ try:
784
+ # Guard extremely large strings (>5MB ~ 6.7M base64 chars) to avoid memory blow
785
+ if len(b64_data) > 7_000_000:
786
+ raise ValueError("Image too large to decode safely")
787
+ with open(file_path, "wb") as f:
788
+ f.write(base64.b64decode(b64_data))
789
+ attachments.append(str(file_path))
790
+ replacement = f"[Image extracted -> {file_path}]"
791
+ except Exception as e:
792
+ self.log(f"Failed to decode embedded image in message {message_id}: {e}", "WARNING")
793
+ replacement = "[Image extraction failed - content omitted]"
794
+ else: # keep
795
+ replacement = placeholder_text # shouldn't be used since early return, but safe
796
+ # Replace only the matched tag (not global) – use re.sub with count=1 on substring slice
797
+ # Safer to operate on new_desc using the exact matched string
798
+ original_tag = match.group(0)
799
+ new_desc = new_desc.replace(original_tag, replacement, 1)
800
+ return new_desc, attachments
801
+ except UnicodeEncodeError as ue:
802
+ self.log(f"Unicode encoding error in process_description: {ue}", "WARNING")
803
+ return desc, []
804
+ except Exception as e:
805
+ self.log(f"Error in process_description: {e}", "WARNING")
806
+ return desc, []
781
807
 
782
808
  try:
783
809
  params = {}
@@ -803,11 +829,18 @@ class BrainApiClient:
803
829
  else:
804
830
  # If changed but no attachments (ignore mode) mark sanitized
805
831
  msg['sanitized'] = True
832
+ except UnicodeEncodeError as ue:
833
+ self.log(f"Unicode encoding error sanitizing message {msg.get('id')}: {ue}", "WARNING")
834
+ # Keep original description if encoding fails
835
+ continue
806
836
  except Exception as inner_e:
807
837
  self.log(f"Failed to sanitize message {msg.get('id')}: {inner_e}", "WARNING")
808
838
  data['results'] = results
809
839
  data['image_handling'] = image_handling
810
840
  return data
841
+ except UnicodeEncodeError as ue:
842
+ self.log(f"Failed to get messages due to encoding error: {str(ue)}", "ERROR")
843
+ raise
811
844
  except Exception as e:
812
845
  self.log(f"Failed to get messages: {str(e)}", "ERROR")
813
846
  raise
@@ -1194,12 +1227,13 @@ class BrainApiClient:
1194
1227
  self.log(f"Failed to get pyramid multipliers: {str(e)}", "ERROR")
1195
1228
  raise
1196
1229
 
1197
- async def get_diversity_score(self, start_date: str, end_date: str) -> Dict[str, Any]:
1230
+ async def value_factor_trendScore(self, start_date: str, end_date: str) -> Dict[str, Any]:
1198
1231
  """Compute diversity score for regular alphas in a date range.
1199
1232
 
1200
1233
  Description:
1201
- This diversity score measures three key aspects of work output: the proportion of works
1202
- with the "Atom" tag (S_A), atom porportion, the breadth of pyramids covered (S_P), and how evenly works
1234
+ This function calculate the diversity of the users' submission, by checking the diversity, we can have a good understanding on the valuefactor's trend.
1235
+ value factor of a user is defiend by This diversity score, which measures three key aspects of work output: the proportion of works
1236
+ with the "Atom" tag (S_A), atom proportion, the breadth of pyramids covered (S_P), and how evenly works
1203
1237
  are distributed across those pyramids (S_H). Calculated as their product, it rewards
1204
1238
  strong performance across all three dimensions—encouraging more Atom-tagged works,
1205
1239
  wider pyramid coverage, and balanced distribution—with weaknesses in any area lowering
@@ -1660,10 +1694,10 @@ async def authenticate(email: Optional[str] = "", password: Optional[str] = "")
1660
1694
 
1661
1695
 
1662
1696
  @mcp.tool()
1663
- async def get_diversity_score(start_date: str, end_date: str) -> Dict[str, Any]:
1697
+ async def value_factor_trendScore(start_date: str, end_date: str) -> Dict[str, Any]:
1664
1698
  """Compute and return the diversity score for REGULAR alphas in a submission-date window.
1665
-
1666
- This MCP tool wraps BrainApiClient.get_diversity_score and always uses submission dates (OS).
1699
+ This function calculate the diversity of the users' submission, by checking the diversity, we can have a good understanding on the valuefactor's trend.
1700
+ This MCP tool wraps BrainApiClient.value_factor_trendScore and always uses submission dates (OS).
1667
1701
 
1668
1702
  Inputs:
1669
1703
  - start_date: ISO UTC start datetime (e.g. '2025-08-14T00:00:00Z')
@@ -1673,7 +1707,7 @@ async def get_diversity_score(start_date: str, end_date: str) -> Dict[str, Any]:
1673
1707
  Returns: compact JSON with diversity_score, N, A, P, P_max, S_A, S_P, S_H, per_pyramid_counts
1674
1708
  """
1675
1709
  try:
1676
- return await brain_client.get_diversity_score(start_date=start_date, end_date=end_date)
1710
+ return await brain_client.value_factor_trendScore(start_date=start_date, end_date=end_date)
1677
1711
  except Exception as e:
1678
1712
  return {"error": str(e)}
1679
1713
 
@@ -2109,9 +2143,9 @@ async def get_documentations() -> Dict[str, Any]:
2109
2143
  # get_messages_summary MCP tool removed as requested
2110
2144
 
2111
2145
  @mcp.tool()
2112
- async def get_messages(limit: Optional[int] = None, offset: int = 0) -> Dict[str, Any]:
2146
+ async def get_messages(limit: Optional[int] = 0, offset: int = 0) -> Dict[str, Any]:
2113
2147
  """
2114
- 💬 Get messages for the current user with optional pagination.
2148
+ Get messages for the current user with optional pagination.
2115
2149
 
2116
2150
  Args:
2117
2151
  limit: Maximum number of messages to return (e.g., 10 for top 10 messages)
@@ -2122,6 +2156,7 @@ async def get_messages(limit: Optional[int] = None, offset: int = 0) -> Dict[str
2122
2156
  Returns:
2123
2157
  Messages for the current user, optionally limited by count
2124
2158
  """
2159
+ # Wrap the entire function in a try-catch to handle any encoding issues
2125
2160
  try:
2126
2161
  # Enhanced parameter validation and conversion
2127
2162
  validated_limit = None
@@ -2161,8 +2196,11 @@ async def get_messages(limit: Optional[int] = None, offset: int = 0) -> Dict[str
2161
2196
  if validated_offset < 0:
2162
2197
  return {"error": f"Offset must be non-negative, got: {offset}"}
2163
2198
 
2164
- # Log the validated parameters for debugging
2165
- print(f"🔍 get_messages called with validated parameters: limit={validated_limit}, offset={validated_offset}")
2199
+ # Log the validated parameters for debugging (without emojis to avoid encoding issues)
2200
+ try:
2201
+ print(f"get_messages called with validated parameters: limit={validated_limit}, offset={validated_offset}")
2202
+ except Exception:
2203
+ print(f"get_messages called with parameters: limit={validated_limit}, offset={validated_offset}")
2166
2204
 
2167
2205
  # Call the brain client with validated parameters
2168
2206
  result = await brain_client.get_messages(validated_limit, validated_offset)
@@ -2182,11 +2220,32 @@ async def get_messages(limit: Optional[int] = None, offset: int = 0) -> Dict[str
2182
2220
 
2183
2221
  return result
2184
2222
 
2223
+ except UnicodeEncodeError as ue:
2224
+ # Handle encoding errors specifically
2225
+ error_msg = f"get_messages failed due to encoding error: {str(ue)}"
2226
+ try:
2227
+ print(f"ENCODING ERROR: {error_msg}")
2228
+ except Exception:
2229
+ print(f"get_messages encoding error: {str(ue)}")
2230
+ return {
2231
+ "error": error_msg,
2232
+ "error_type": "UnicodeEncodeError",
2233
+ "original_params": {
2234
+ "limit": limit,
2235
+ "offset": offset,
2236
+ "limit_type": str(type(limit)),
2237
+ "offset_type": str(type(offset))
2238
+ }
2239
+ }
2185
2240
  except Exception as e:
2186
2241
  error_msg = f"get_messages failed: {str(e)}"
2187
- print(f"❌ {error_msg}")
2242
+ try:
2243
+ print(f"ERROR: {error_msg}")
2244
+ except Exception:
2245
+ print(f"get_messages failed: {str(e)}")
2188
2246
  return {
2189
2247
  "error": error_msg,
2248
+ "error_type": type(e).__name__,
2190
2249
  "original_params": {
2191
2250
  "limit": limit,
2192
2251
  "offset": offset,
@@ -2779,6 +2838,10 @@ async def get_SimError_detail(locations: Sequence[str]) -> dict:
2779
2838
  return {"results": results}
2780
2839
 
2781
2840
  if __name__ == "__main__":
2782
- print("WorldQuant BRAIN MCP Server Starting...", file=sys.stderr)
2783
- mcp.run()
2841
+ try:
2842
+ print("WorldQuant BRAIN MCP Server Starting...", file=sys.stderr)
2843
+ mcp.run()
2844
+ except Exception as e:
2845
+ print(f"Failed to start MCP server: {e}", file=sys.stderr)
2846
+ sys.exit(1)
2784
2847
 
@@ -34,11 +34,11 @@
34
34
  2. **社区动态**:从消息中提取社区相关信息,如研究论文或热门话题。
35
35
  3. **排行榜变化**:记录用户位置变化。
36
36
  - 使用工具:`mcp_brain-api_get_leaderboard`(设置 `user_id` 为用户 ID,如 "CQ89422")。
37
- 4. **多样性分数**:收集用户最近一个季度的多样性分数,提出具体建议。
37
+ 4. **多样性分数**:收集用户最近一个季度的多样性分数,获知其value factor趋势,该分数捕捉用户提交Alpha的多样性,来判断其value factor的变化趋势,在0-1之间,越高越好,据此提出具体建议。
38
38
  - **使用的 MCP 工具**:
39
39
  - `mcp_brain-api_get_messages`:获取平台公告和社区动态。
40
40
  - `mcp_brain-api_get_leaderboard`:获取用户排行榜统计。
41
- - `mcp_brain-api_get_diversity_scores`:获取用户多样性分数。
41
+ - `mcp_brain-api_value_factor_trendScore`:用户value factor趋势,又名多样性分数。
42
42
 
43
43
  ### 3. 比赛参与与进度
44
44
  - **步骤**:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cnhkmcp
3
- Version: 1.4.0
3
+ Version: 1.4.2
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
@@ -1,8 +1,8 @@
1
- cnhkmcp/__init__.py,sha256=sziPMtlpV5JLwaObeqvLxL2cZIeUtMs7uyod9zaoOMg,2758
1
+ cnhkmcp/__init__.py,sha256=wqKNYTIOL4EgYpqCzY0MEiTP74yW4ZY6d01KFoo3GU0,2758
2
2
  cnhkmcp/untracked/arXiv_API_Tool_Manual.md,sha256=I3hvI5mpmIjBuWptufoVSWFnuhyUc67oCGHEuR0p_xs,13552
3
3
  cnhkmcp/untracked/arxiv_api.py,sha256=-E-Ub9K-DXAYaCjrbobyfQ9H97gaZBc7pL6xPEyVHec,9020
4
4
  cnhkmcp/untracked/forum_functions.py,sha256=QW-CplAsqDkw-Wcwq-1tuZBq48dEO-vXZ8xw7X65EuE,42303
5
- cnhkmcp/untracked/platform_functions.py,sha256=ba_rvLNvnGk5vafwIkWPddLd0coDXCSraut0QgRl7ik,116467
5
+ cnhkmcp/untracked/platform_functions.py,sha256=lf3i7q7zZarIX4fUgutm8TmU82QKtKU1JxPvUElIj_o,119842
6
6
  cnhkmcp/untracked/sample_mcp_config.json,sha256=QSFvZ086bxUQsvmLjcE6pL9ObzKn4FGnt9npWPo7Eps,1044
7
7
  cnhkmcp/untracked/user_config.json,sha256=_INn1X1qIsITrmEno-BRlQOAGm9wnNCw-6B333DEvnk,695
8
8
  cnhkmcp/untracked/示例参考文档_BRAIN_Alpha_Test_Requirements_and_Tips.md,sha256=W4dtQrqoTN72UyvIsvkGRF0HFOJLHSDeeSlbR3gqQg0,17133
@@ -10,7 +10,7 @@ cnhkmcp/untracked/示例工作流_Alpha_explaination_workflow.md,sha256=QukeT9gI
10
10
  cnhkmcp/untracked/示例工作流_BRAIN_6_Tips_Datafield_Exploration_Guide.md,sha256=YyMX1MIsDTJTduxulY-fYipNHvRihshQy9Q2j6Zxg2Q,9123
11
11
  cnhkmcp/untracked/示例工作流_BRAIN_Alpha_Improvement_Workflow.md,sha256=XlWYREd_qXe1skdXIhkiGY05oDr_6KiBs1WkerY4S8U,5092
12
12
  cnhkmcp/untracked/示例工作流_Dataset_Exploration_Expert_Manual.md,sha256=-C4fWdaBe9UzA5BDZz0Do2z8RaPWLslb6D0nTz6fqk4,24403
13
- cnhkmcp/untracked/示例工作流_daily_report_workflow.md,sha256=tqB0L27xJe9JCPIpWqnuTD3GdfMywMa0B0mR3jda3Gs,9281
13
+ cnhkmcp/untracked/示例工作流_daily_report_workflow.md,sha256=6aNmPqWRn09XdQMRxoVTka9IYVOUv5LcWparHu16EfQ,9460
14
14
  cnhkmcp/untracked/APP/.gitignore,sha256=oPCoVTNo82bhkN0c671LdjCpOTVpVhZI5NR75ztcg48,317
15
15
  cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md,sha256=Ji4VeRZjeMWRX6cvEHxyR_gmoorIEEdqwsXTCVIr5_0,4331
16
16
  cnhkmcp/untracked/APP/README.md,sha256=vb7hmQX0sH5aFNBmDCN5szMSDHm1_h2VKY4UKCt0aMk,11676
@@ -60,9 +60,9 @@ cnhkmcp/untracked/APP/templates/inspiration_house.html,sha256=CZMHj_ild-mUDRvNc5
60
60
  cnhkmcp/untracked/APP/templates/paper_analysis.html,sha256=7HgltBpZtmZCYCF7JU4wU9F5NMxUKyCXg7bCYCRfQDs,4139
61
61
  cnhkmcp/untracked/APP/templates/simulator.html,sha256=Y3-w-mZyvRiOdBiFe705CqBPAUI07KNY2WJ5meNzHM8,12508
62
62
  cnhkmcp/untracked/__pycache__/forum_functions.cpython-313.pyc,sha256=agARlP2x36scUU0TIYnrHB3EpEx4OOuHrB42_B1XR3k,45049
63
- cnhkmcp-1.4.0.dist-info/licenses/LICENSE,sha256=QLxO2eNMnJQEdI_R1UV2AOD-IvuA8zVrkHWA4D9gtoc,1081
64
- cnhkmcp-1.4.0.dist-info/METADATA,sha256=h0ZnKYlZFYIzZDtDX7VUCX9YLR0VAm9ivblz6Cj7W20,5171
65
- cnhkmcp-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
66
- cnhkmcp-1.4.0.dist-info/entry_points.txt,sha256=lTQieVyIvjhSMK4fT-XwnccY-JBC1H4vVQ3V9dDM-Pc,70
67
- cnhkmcp-1.4.0.dist-info/top_level.txt,sha256=x--ibUcSgOS9Z_RWK2Qc-vfs7DaXQN-WMaaxEETJ1Bw,8
68
- cnhkmcp-1.4.0.dist-info/RECORD,,
63
+ cnhkmcp-1.4.2.dist-info/licenses/LICENSE,sha256=QLxO2eNMnJQEdI_R1UV2AOD-IvuA8zVrkHWA4D9gtoc,1081
64
+ cnhkmcp-1.4.2.dist-info/METADATA,sha256=QB_7K08v3JYQQVvg57c85NoiZrnD9A-Qv_k9Fx1gCm8,5171
65
+ cnhkmcp-1.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
66
+ cnhkmcp-1.4.2.dist-info/entry_points.txt,sha256=lTQieVyIvjhSMK4fT-XwnccY-JBC1H4vVQ3V9dDM-Pc,70
67
+ cnhkmcp-1.4.2.dist-info/top_level.txt,sha256=x--ibUcSgOS9Z_RWK2Qc-vfs7DaXQN-WMaaxEETJ1Bw,8
68
+ cnhkmcp-1.4.2.dist-info/RECORD,,