futu-stock-mcp-server 0.1.3__py3-none-any.whl → 0.1.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.
Potentially problematic release.
This version of futu-stock-mcp-server might be problematic. Click here for more details.
- futu_stock_mcp_server/server.py +286 -41
- {futu_stock_mcp_server-0.1.3.dist-info → futu_stock_mcp_server-0.1.4.dist-info}/METADATA +1 -2
- futu_stock_mcp_server-0.1.4.dist-info/RECORD +7 -0
- futu_stock_mcp_server-0.1.3.dist-info/RECORD +0 -7
- {futu_stock_mcp_server-0.1.3.dist-info → futu_stock_mcp_server-0.1.4.dist-info}/WHEEL +0 -0
- {futu_stock_mcp_server-0.1.3.dist-info → futu_stock_mcp_server-0.1.4.dist-info}/entry_points.txt +0 -0
- {futu_stock_mcp_server-0.1.3.dist-info → futu_stock_mcp_server-0.1.4.dist-info}/licenses/LICENSE +0 -0
futu_stock_mcp_server/server.py
CHANGED
|
@@ -1,12 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import warnings
|
|
4
|
+
import logging
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
# CRITICAL: Check if this is a help command before setting MCP mode
|
|
8
|
+
_is_help_command = any(arg in ['--help', '-h', '--version', '-v'] for arg in sys.argv)
|
|
9
|
+
|
|
10
|
+
# CRITICAL: Set MCP mode BEFORE any logging to ensure clean stdout
|
|
11
|
+
# But not if this is a help command - in that case, we want normal stdout
|
|
12
|
+
if not _is_help_command:
|
|
13
|
+
os.environ['MCP_MODE'] = os.environ.get('MCP_MODE', '1')
|
|
14
|
+
|
|
15
|
+
# CRITICAL: Completely disable all potential stdout pollution sources
|
|
16
|
+
# This must be done BEFORE any other imports or operations
|
|
17
|
+
|
|
18
|
+
# 1. Disable all warnings that might go to stdout/stderr
|
|
19
|
+
warnings.filterwarnings("ignore")
|
|
20
|
+
warnings.simplefilter("ignore")
|
|
21
|
+
|
|
22
|
+
# 2. Completely disable the standard logging system
|
|
23
|
+
logging.disable(logging.CRITICAL)
|
|
24
|
+
|
|
25
|
+
# 3. Set environment variables to prevent ANSI escape sequences
|
|
26
|
+
os.environ['NO_COLOR'] = '1'
|
|
27
|
+
os.environ['TERM'] = 'dumb'
|
|
28
|
+
os.environ['FORCE_COLOR'] = '0'
|
|
29
|
+
os.environ['COLORTERM'] = ''
|
|
30
|
+
os.environ['ANSI_COLORS_DISABLED'] = '1'
|
|
31
|
+
os.environ['PYTHONUNBUFFERED'] = '1'
|
|
32
|
+
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
|
33
|
+
|
|
34
|
+
# 4. Create a custom stdout wrapper to catch any accidental writes
|
|
35
|
+
class StdoutProtector:
|
|
36
|
+
"""Protects stdout from any non-MCP content"""
|
|
37
|
+
def __init__(self, original_stdout):
|
|
38
|
+
self.original = original_stdout
|
|
39
|
+
self.buffer = ""
|
|
40
|
+
|
|
41
|
+
def write(self, text):
|
|
42
|
+
# Only allow JSON-like content or empty strings
|
|
43
|
+
if not text or text.isspace():
|
|
44
|
+
self.original.write(text)
|
|
45
|
+
elif text.strip().startswith(('{', '[', '"')) or text.strip() == '':
|
|
46
|
+
self.original.write(text)
|
|
47
|
+
else:
|
|
48
|
+
# Silently drop non-JSON content
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def flush(self):
|
|
52
|
+
self.original.flush()
|
|
53
|
+
|
|
54
|
+
def __getattr__(self, name):
|
|
55
|
+
return getattr(self.original, name)
|
|
56
|
+
|
|
57
|
+
# Apply stdout protection in MCP mode VERY EARLY, before any imports
|
|
58
|
+
if os.getenv('MCP_MODE') == '1':
|
|
59
|
+
sys.stdout = StdoutProtector(sys.stdout)
|
|
60
|
+
|
|
61
|
+
# 5. Redirect stderr to null in MCP mode to prevent any accidental output
|
|
62
|
+
_stderr_redirected = False
|
|
63
|
+
_stderr_backup = None
|
|
64
|
+
if os.getenv('MCP_MODE') == '1':
|
|
65
|
+
# Save original stderr for emergency use
|
|
66
|
+
_stderr_backup = sys.stderr
|
|
67
|
+
# Redirect stderr to devnull
|
|
68
|
+
devnull = open(os.devnull, 'w')
|
|
69
|
+
sys.stderr = devnull
|
|
70
|
+
_stderr_redirected = True
|
|
71
|
+
|
|
72
|
+
# Now we can safely import other modules
|
|
1
73
|
from contextlib import asynccontextmanager
|
|
2
74
|
from collections.abc import AsyncIterator
|
|
3
75
|
from typing import Dict, Any, List, Optional
|
|
4
|
-
|
|
76
|
+
try:
|
|
77
|
+
from futu import OpenQuoteContext, OpenSecTradeContext, TrdMarket, SecurityFirm, RET_OK
|
|
78
|
+
except ImportError as e:
|
|
79
|
+
# In MCP mode, we should avoid printing to stdout/stderr
|
|
80
|
+
# Log to file only
|
|
81
|
+
logger.error(f"Failed to import futu: {e}")
|
|
82
|
+
sys.exit(1)
|
|
5
83
|
import json
|
|
6
84
|
import asyncio
|
|
7
85
|
from loguru import logger
|
|
8
|
-
import os
|
|
9
|
-
import sys
|
|
10
86
|
from dotenv import load_dotenv
|
|
11
87
|
from mcp.server.fastmcp import FastMCP, Context
|
|
12
88
|
from mcp.types import TextContent, PromptMessage
|
|
@@ -18,16 +94,11 @@ import fcntl
|
|
|
18
94
|
import psutil
|
|
19
95
|
import time
|
|
20
96
|
from datetime import datetime
|
|
21
|
-
import logging
|
|
22
|
-
import warnings
|
|
23
97
|
|
|
24
|
-
# Get the
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# Load environment variables from project root
|
|
29
|
-
env_path = os.path.join(project_root, '.env')
|
|
30
|
-
load_dotenv(env_path)
|
|
98
|
+
# Get the user home directory and create logs directory there
|
|
99
|
+
home_dir = os.path.expanduser("~")
|
|
100
|
+
log_dir = os.path.join(home_dir, "logs")
|
|
101
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
31
102
|
|
|
32
103
|
# CRITICAL: Configure logging to be MCP-compatible
|
|
33
104
|
# According to MCP best practices, we should:
|
|
@@ -42,24 +113,36 @@ warnings.filterwarnings("ignore")
|
|
|
42
113
|
# Configure loguru for file-only logging
|
|
43
114
|
logger.remove() # Remove all default handlers
|
|
44
115
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# Add file handler
|
|
50
|
-
logger.add(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
116
|
+
# CRITICAL: In MCP mode, ensure NO stderr output at all
|
|
117
|
+
if os.getenv('MCP_MODE') == '1':
|
|
118
|
+
# Remove any remaining handlers that might output to stderr
|
|
119
|
+
logger.remove()
|
|
120
|
+
# Add only file handler - NO console output
|
|
121
|
+
logger.add(
|
|
122
|
+
os.path.join(log_dir, "futu_mcp_server.log"),
|
|
123
|
+
rotation="500 MB",
|
|
124
|
+
retention="10 days",
|
|
125
|
+
level="DEBUG",
|
|
126
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name} | {message}",
|
|
127
|
+
enqueue=True, # Thread-safe logging
|
|
128
|
+
backtrace=True,
|
|
129
|
+
diagnose=True
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
# Non-MCP mode: add file handler
|
|
133
|
+
logger.add(
|
|
134
|
+
os.path.join(log_dir, "futu_mcp_server.log"),
|
|
135
|
+
rotation="500 MB",
|
|
136
|
+
retention="10 days",
|
|
137
|
+
level="DEBUG",
|
|
138
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name} | {message}",
|
|
139
|
+
enqueue=True, # Thread-safe logging
|
|
140
|
+
backtrace=True,
|
|
141
|
+
diagnose=True
|
|
142
|
+
)
|
|
60
143
|
|
|
61
144
|
# Only add stderr logging if explicitly in debug mode and not in MCP mode
|
|
62
|
-
if os.getenv('FUTU_DEBUG_MODE') == '1' and not os.getenv('MCP_MODE'):
|
|
145
|
+
if os.getenv('FUTU_DEBUG_MODE') == '1' and not os.getenv('MCP_MODE') == '1':
|
|
63
146
|
logger.add(
|
|
64
147
|
sys.stderr,
|
|
65
148
|
level="INFO",
|
|
@@ -92,6 +175,45 @@ for logger_name in [
|
|
|
92
175
|
lib_logger.setLevel(logging.CRITICAL + 1)
|
|
93
176
|
lib_logger.propagate = False
|
|
94
177
|
|
|
178
|
+
# Even more aggressive suppression for futu library
|
|
179
|
+
# This is critical because futu library may output logs during connection
|
|
180
|
+
try:
|
|
181
|
+
# Suppress futu library logging completely
|
|
182
|
+
futu_logger = logging.getLogger('futu')
|
|
183
|
+
futu_logger.disabled = True
|
|
184
|
+
futu_logger.setLevel(logging.CRITICAL + 1)
|
|
185
|
+
futu_logger.propagate = False
|
|
186
|
+
|
|
187
|
+
# Suppress specific futu sub-modules that are known to output logs
|
|
188
|
+
for sub_logger_name in [
|
|
189
|
+
'futu', 'futu.common', 'futu.quote', 'futu.trade',
|
|
190
|
+
'futu.common.constant', 'futu.common.sys_utils',
|
|
191
|
+
'futu.quote.open_quote_context', 'futu.quote.quote_response_handler',
|
|
192
|
+
'futu.trade.open_trade_context', 'futu.trade.trade_response_handler',
|
|
193
|
+
'futu.common.open_context_base', 'futu.common.network_manager'
|
|
194
|
+
]:
|
|
195
|
+
sub_logger = logging.getLogger(sub_logger_name)
|
|
196
|
+
sub_logger.disabled = True
|
|
197
|
+
sub_logger.setLevel(logging.CRITICAL + 1)
|
|
198
|
+
sub_logger.propagate = False
|
|
199
|
+
|
|
200
|
+
# Also redirect any direct print statements from futu to a file
|
|
201
|
+
if os.getenv('MCP_MODE') == '1':
|
|
202
|
+
# Create a special log file for futu connection logs
|
|
203
|
+
home_dir = os.path.expanduser("~")
|
|
204
|
+
log_dir = os.path.join(home_dir, "logs")
|
|
205
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
206
|
+
|
|
207
|
+
futu_conn_log_file = os.path.join(log_dir, "futu_connection.log")
|
|
208
|
+
futu_conn_log = open(futu_conn_log_file, 'a')
|
|
209
|
+
|
|
210
|
+
# This is a last resort to catch any print statements from futu
|
|
211
|
+
# We'll redirect stderr to this file temporarily during connection
|
|
212
|
+
# and then restore it to devnull
|
|
213
|
+
except Exception as e:
|
|
214
|
+
# If we can't set up additional logging, continue anyway
|
|
215
|
+
pass
|
|
216
|
+
|
|
95
217
|
# MCP-compatible logging helper functions
|
|
96
218
|
async def log_to_mcp(ctx: Context, level: str, message: str):
|
|
97
219
|
"""Send log message through MCP Context when available"""
|
|
@@ -116,7 +238,7 @@ def safe_log(level: str, message: str, ctx: Context = None):
|
|
|
116
238
|
logger.log(level.upper(), message)
|
|
117
239
|
|
|
118
240
|
# Also send to MCP if context is available
|
|
119
|
-
if ctx:
|
|
241
|
+
if ctx and os.getenv('MCP_MODE') == '1' and not _stderr_redirected:
|
|
120
242
|
try:
|
|
121
243
|
import asyncio
|
|
122
244
|
loop = asyncio.get_event_loop()
|
|
@@ -125,8 +247,11 @@ def safe_log(level: str, message: str, ctx: Context = None):
|
|
|
125
247
|
except Exception:
|
|
126
248
|
pass # Ignore MCP logging errors
|
|
127
249
|
|
|
128
|
-
|
|
129
|
-
|
|
250
|
+
# Only log to file, never to stdout/stderr in MCP mode
|
|
251
|
+
# These logs will only be written to file, not to stdout/stderr
|
|
252
|
+
|
|
253
|
+
# Get project root directory
|
|
254
|
+
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
130
255
|
|
|
131
256
|
# PID file path
|
|
132
257
|
PID_FILE = os.path.join(project_root, '.futu_mcp.pid')
|
|
@@ -429,9 +554,103 @@ def init_trade_connection():
|
|
|
429
554
|
_is_trade_initialized = False
|
|
430
555
|
return False
|
|
431
556
|
|
|
432
|
-
def init_futu_connection():
|
|
433
|
-
"""
|
|
434
|
-
|
|
557
|
+
def init_futu_connection() -> bool:
|
|
558
|
+
"""
|
|
559
|
+
Initialize connection to Futu OpenD.
|
|
560
|
+
Returns True if successful, False otherwise.
|
|
561
|
+
"""
|
|
562
|
+
global quote_ctx, trade_ctx, _is_trade_initialized
|
|
563
|
+
|
|
564
|
+
try:
|
|
565
|
+
# Get connection parameters from environment
|
|
566
|
+
host = os.getenv('FUTU_HOST', '127.0.0.1')
|
|
567
|
+
port = int(os.getenv('FUTU_PORT', '11111'))
|
|
568
|
+
|
|
569
|
+
# Log to file only
|
|
570
|
+
logger.info(f"Initializing Futu connection to {host}:{port}")
|
|
571
|
+
|
|
572
|
+
# Temporarily redirect stderr to capture any futu library output
|
|
573
|
+
original_stderr = None
|
|
574
|
+
futu_log_file = None
|
|
575
|
+
futu_log_fd = None
|
|
576
|
+
|
|
577
|
+
if os.getenv('MCP_MODE') == '1' and _stderr_redirected:
|
|
578
|
+
try:
|
|
579
|
+
# Create a special log file for futu connection logs
|
|
580
|
+
home_dir = os.path.expanduser("~")
|
|
581
|
+
log_dir = os.path.join(home_dir, "logs")
|
|
582
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
583
|
+
futu_log_file = os.path.join(log_dir, "futu_connection.log")
|
|
584
|
+
|
|
585
|
+
# Save current stderr (should be devnull)
|
|
586
|
+
original_stderr = sys.stderr
|
|
587
|
+
|
|
588
|
+
# Redirect stderr to futu log file temporarily
|
|
589
|
+
futu_log_fd = open(futu_log_file, 'a')
|
|
590
|
+
sys.stderr = futu_log_fd
|
|
591
|
+
except Exception as e:
|
|
592
|
+
logger.debug(f"Could not redirect stderr for futu connection: {e}")
|
|
593
|
+
|
|
594
|
+
try:
|
|
595
|
+
# Initialize quote context
|
|
596
|
+
quote_ctx = OpenQuoteContext(host=host, port=port)
|
|
597
|
+
|
|
598
|
+
# Initialize trade context if needed
|
|
599
|
+
if os.getenv('FUTU_ENABLE_TRADING', '0') == '1':
|
|
600
|
+
# Get trading parameters
|
|
601
|
+
trade_env = os.getenv('FUTU_TRADE_ENV', 'SIMULATE')
|
|
602
|
+
security_firm = os.getenv('FUTU_SECURITY_FIRM', 'FUTUSECURITIES')
|
|
603
|
+
trd_market = os.getenv('FUTU_TRD_MARKET', 'HK')
|
|
604
|
+
|
|
605
|
+
# Map environment strings to Futu enums
|
|
606
|
+
trade_env_enum = {
|
|
607
|
+
'REAL': TrdMarket.REAL,
|
|
608
|
+
'SIMULATE': TrdMarket.SIMULATE
|
|
609
|
+
}.get(trade_env, TrdMarket.SIMULATE)
|
|
610
|
+
|
|
611
|
+
security_firm_enum = {
|
|
612
|
+
'FUTUSECURITIES': SecurityFirm.FUTUSECURITIES,
|
|
613
|
+
'FUTUINC': SecurityFirm.FUTUINC
|
|
614
|
+
}.get(security_firm, SecurityFirm.FUTUSECURITIES)
|
|
615
|
+
|
|
616
|
+
trd_market_enum = {
|
|
617
|
+
'HK': TrdMarket.HK,
|
|
618
|
+
'US': TrdMarket.US,
|
|
619
|
+
'CN': TrdMarket.CN,
|
|
620
|
+
'HKCC': TrdMarket.HKCC,
|
|
621
|
+
'AU': TrdMarket.AU
|
|
622
|
+
}.get(trd_market, TrdMarket.HK)
|
|
623
|
+
|
|
624
|
+
# Initialize trade context
|
|
625
|
+
trade_ctx = OpenSecTradeContext(
|
|
626
|
+
host=host,
|
|
627
|
+
port=port,
|
|
628
|
+
security_firm=security_firm_enum
|
|
629
|
+
)
|
|
630
|
+
_is_trade_initialized = True
|
|
631
|
+
logger.info("Trade context initialized successfully")
|
|
632
|
+
|
|
633
|
+
logger.info("Futu connection initialized successfully")
|
|
634
|
+
return True
|
|
635
|
+
|
|
636
|
+
finally:
|
|
637
|
+
# Restore stderr redirection
|
|
638
|
+
if futu_log_fd:
|
|
639
|
+
try:
|
|
640
|
+
futu_log_fd.close()
|
|
641
|
+
except:
|
|
642
|
+
pass
|
|
643
|
+
# Restore original stderr (should be devnull)
|
|
644
|
+
if original_stderr:
|
|
645
|
+
sys.stderr = original_stderr
|
|
646
|
+
|
|
647
|
+
except Exception as e:
|
|
648
|
+
error_msg = f"Failed to initialize Futu connection: {str(e)}"
|
|
649
|
+
logger.error(error_msg)
|
|
650
|
+
# Make sure we restore stderr even in case of errors
|
|
651
|
+
if _stderr_redirected and _stderr_backup:
|
|
652
|
+
sys.stderr = _stderr_backup
|
|
653
|
+
return False
|
|
435
654
|
|
|
436
655
|
@asynccontextmanager
|
|
437
656
|
async def lifespan(server: Server):
|
|
@@ -1554,6 +1773,34 @@ async def get_current_time() -> Dict[str, Any]:
|
|
|
1554
1773
|
|
|
1555
1774
|
def main():
|
|
1556
1775
|
"""Main entry point for the futu-mcp-server command."""
|
|
1776
|
+
# Parse command line arguments first
|
|
1777
|
+
parser = argparse.ArgumentParser(
|
|
1778
|
+
description="Futu Stock MCP Server - A Model Context Protocol server for accessing Futu OpenAPI functionality",
|
|
1779
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1780
|
+
epilog="""
|
|
1781
|
+
Examples:
|
|
1782
|
+
futu-mcp-server # Start the MCP server
|
|
1783
|
+
futu-mcp-server --help # Show this help message
|
|
1784
|
+
|
|
1785
|
+
Environment Variables:
|
|
1786
|
+
FUTU_HOST # Futu OpenD host (default: 127.0.0.1)
|
|
1787
|
+
FUTU_PORT # Futu OpenD port (default: 11111)
|
|
1788
|
+
FUTU_ENABLE_TRADING # Enable trading features (default: 0)
|
|
1789
|
+
FUTU_TRADE_ENV # Trading environment: SIMULATE or REAL (default: SIMULATE)
|
|
1790
|
+
FUTU_SECURITY_FIRM # Security firm: FUTUSECURITIES or FUTUINC (default: FUTUSECURITIES)
|
|
1791
|
+
FUTU_TRD_MARKET # Trading market: HK or US (default: HK)
|
|
1792
|
+
FUTU_DEBUG_MODE # Enable debug logging (default: 0)
|
|
1793
|
+
"""
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1796
|
+
parser.add_argument(
|
|
1797
|
+
'--version',
|
|
1798
|
+
action='version',
|
|
1799
|
+
version='futu-stock-mcp-server 0.1.3'
|
|
1800
|
+
)
|
|
1801
|
+
|
|
1802
|
+
args = parser.parse_args()
|
|
1803
|
+
|
|
1557
1804
|
try:
|
|
1558
1805
|
# CRITICAL: Set MCP mode BEFORE any logging to ensure clean stdout
|
|
1559
1806
|
os.environ['MCP_MODE'] = '1'
|
|
@@ -1564,19 +1811,14 @@ def main():
|
|
|
1564
1811
|
os.environ['FORCE_COLOR'] = '0'
|
|
1565
1812
|
os.environ['COLORTERM'] = ''
|
|
1566
1813
|
os.environ['ANSI_COLORS_DISABLED'] = '1'
|
|
1567
|
-
|
|
1568
|
-
# Disable Python buffering to ensure clean MCP JSON communication
|
|
1569
1814
|
os.environ['PYTHONUNBUFFERED'] = '1'
|
|
1570
1815
|
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
|
1571
1816
|
|
|
1572
|
-
#
|
|
1573
|
-
# - stdout is RESERVED for MCP JSON communication only
|
|
1574
|
-
# - All logging should go to files
|
|
1575
|
-
# - No stderr output in production mode to avoid pollution
|
|
1817
|
+
# Disable Python buffering to ensure clean MCP JSON communication
|
|
1576
1818
|
|
|
1577
1819
|
# Clean up stale processes and acquire lock
|
|
1578
1820
|
cleanup_stale_processes()
|
|
1579
|
-
|
|
1821
|
+
|
|
1580
1822
|
lock_fd = acquire_lock()
|
|
1581
1823
|
if lock_fd is None:
|
|
1582
1824
|
# Use file logging only - no stderr output in MCP mode
|
|
@@ -1609,6 +1851,8 @@ def main():
|
|
|
1609
1851
|
os._exit(1)
|
|
1610
1852
|
|
|
1611
1853
|
except Exception as e:
|
|
1854
|
+
# In MCP mode, we should avoid printing to stdout
|
|
1855
|
+
# Log to file only
|
|
1612
1856
|
logger.error(f"Error starting MCP server: {str(e)}")
|
|
1613
1857
|
sys.exit(1)
|
|
1614
1858
|
finally:
|
|
@@ -1616,3 +1860,4 @@ def main():
|
|
|
1616
1860
|
|
|
1617
1861
|
if __name__ == "__main__":
|
|
1618
1862
|
main()
|
|
1863
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: futu-stock-mcp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A Model Context Protocol (MCP) server for accessing Futu OpenAPI functionality
|
|
5
5
|
Project-URL: Homepage, https://github.com/shuizhengqi1/futu-stock-mcp-server
|
|
6
6
|
Project-URL: Documentation, https://github.com/shuizhengqi1/futu-stock-mcp-server#readme
|
|
@@ -40,7 +40,6 @@ Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
|
40
40
|
Requires-Dist: ruff; extra == 'dev'
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
|
|
43
|
-
[](https://mseep.ai/app/shuizhengqi1-futu-stocp-mcp-server)
|
|
44
43
|
|
|
45
44
|
# Futu Stock MCP Server
|
|
46
45
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
futu_stock_mcp_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
futu_stock_mcp_server/server.py,sha256=SokGZ3seYN1KwNozJqpbAmRsjcoYVSJ8v_wvMHu3pcs,66611
|
|
3
|
+
futu_stock_mcp_server-0.1.4.dist-info/METADATA,sha256=V8KxUH0ehGGfS7FtOGMH6t-kk1NYkKHhFLi3Vg84FO4,21041
|
|
4
|
+
futu_stock_mcp_server-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
+
futu_stock_mcp_server-0.1.4.dist-info/entry_points.txt,sha256=GAdKqPJD9dJ_fRA3e3m0NRia0elN5OcjEeAI30vOcIM,70
|
|
6
|
+
futu_stock_mcp_server-0.1.4.dist-info/licenses/LICENSE,sha256=XQBSQkjjpkymu_uLdyis4oNynV60VH1X7nS16uwM6g0,1069
|
|
7
|
+
futu_stock_mcp_server-0.1.4.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
futu_stock_mcp_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
futu_stock_mcp_server/server.py,sha256=d03R4mc1eKNNX1JhYDnbj59NRN12O8XG_oc_T8mNVJU,57336
|
|
3
|
-
futu_stock_mcp_server-0.1.3.dist-info/METADATA,sha256=PeSU1LZfoEXaThL6W-ZUhQMr7VR9JcWNQp6M6i56Or8,21205
|
|
4
|
-
futu_stock_mcp_server-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
-
futu_stock_mcp_server-0.1.3.dist-info/entry_points.txt,sha256=GAdKqPJD9dJ_fRA3e3m0NRia0elN5OcjEeAI30vOcIM,70
|
|
6
|
-
futu_stock_mcp_server-0.1.3.dist-info/licenses/LICENSE,sha256=XQBSQkjjpkymu_uLdyis4oNynV60VH1X7nS16uwM6g0,1069
|
|
7
|
-
futu_stock_mcp_server-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
{futu_stock_mcp_server-0.1.3.dist-info → futu_stock_mcp_server-0.1.4.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{futu_stock_mcp_server-0.1.3.dist-info → futu_stock_mcp_server-0.1.4.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|