futu-stock-mcp-server 0.1.5__tar.gz → 0.1.6__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.
Potentially problematic release.
This version of futu-stock-mcp-server might be problematic. Click here for more details.
- {futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/PKG-INFO +1 -1
- {futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/pyproject.toml +1 -1
- {futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/src/futu_stock_mcp_server/server.py +61 -32
- {futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/.gitignore +0 -0
- {futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/LICENSE +0 -0
- {futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/README.md +0 -0
- {futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/src/futu_stock_mcp_server/__init__.py +0 -0
|
@@ -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.6
|
|
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
|
{futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/src/futu_stock_mcp_server/server.py
RENAMED
|
@@ -51,23 +51,29 @@ class StdoutProtector:
|
|
|
51
51
|
def flush(self):
|
|
52
52
|
self.original.flush()
|
|
53
53
|
|
|
54
|
+
def readable(self):
|
|
55
|
+
"""Delegate readable() method to original stdout"""
|
|
56
|
+
return getattr(self.original, 'readable', lambda: False)()
|
|
57
|
+
|
|
58
|
+
def writable(self):
|
|
59
|
+
"""Delegate writable() method to original stdout"""
|
|
60
|
+
return getattr(self.original, 'writable', lambda: True)()
|
|
61
|
+
|
|
62
|
+
def seekable(self):
|
|
63
|
+
"""Delegate seekable() method to original stdout"""
|
|
64
|
+
return getattr(self.original, 'seekable', lambda: False)()
|
|
65
|
+
|
|
54
66
|
def __getattr__(self, name):
|
|
55
67
|
return getattr(self.original, name)
|
|
56
68
|
|
|
57
69
|
# Apply stdout protection in MCP mode VERY EARLY, before any imports
|
|
58
70
|
if os.getenv('MCP_MODE') == '1':
|
|
59
|
-
sys.stdout = StdoutProtector(sys.stdout)
|
|
71
|
+
# sys.stdout = StdoutProtector(sys.stdout)
|
|
60
72
|
|
|
61
|
-
# 5.
|
|
73
|
+
# 5. Don't redirect stderr in MCP mode - let it work normally
|
|
74
|
+
# MCP servers can use stderr for logging, only stdout needs protection
|
|
62
75
|
_stderr_redirected = False
|
|
63
76
|
_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
77
|
|
|
72
78
|
# Now we can safely import other modules
|
|
73
79
|
from contextlib import asynccontextmanager
|
|
@@ -263,6 +269,8 @@ trade_ctx = None
|
|
|
263
269
|
lock_fd = None
|
|
264
270
|
_is_shutting_down = False
|
|
265
271
|
_is_trade_initialized = False
|
|
272
|
+
_futu_host = '127.0.0.1'
|
|
273
|
+
_futu_port = 11111
|
|
266
274
|
|
|
267
275
|
def is_process_running(pid):
|
|
268
276
|
"""Check if a process with given PID is running"""
|
|
@@ -436,14 +444,14 @@ def acquire_lock():
|
|
|
436
444
|
|
|
437
445
|
def init_quote_connection():
|
|
438
446
|
"""Initialize quote connection only"""
|
|
439
|
-
global quote_ctx
|
|
447
|
+
global quote_ctx, _futu_host, _futu_port
|
|
440
448
|
|
|
441
449
|
try:
|
|
442
450
|
# Check if OpenD is running by attempting to get global state
|
|
443
451
|
try:
|
|
444
452
|
temp_ctx = OpenQuoteContext(
|
|
445
|
-
host=
|
|
446
|
-
port=
|
|
453
|
+
host=_futu_host,
|
|
454
|
+
port=_futu_port
|
|
447
455
|
)
|
|
448
456
|
ret, _ = temp_ctx.get_global_state()
|
|
449
457
|
temp_ctx.close()
|
|
@@ -456,8 +464,8 @@ def init_quote_connection():
|
|
|
456
464
|
|
|
457
465
|
# Initialize Futu connection
|
|
458
466
|
quote_ctx = OpenQuoteContext(
|
|
459
|
-
host=
|
|
460
|
-
port=
|
|
467
|
+
host=_futu_host,
|
|
468
|
+
port=_futu_port
|
|
461
469
|
)
|
|
462
470
|
logger.info("Successfully connected to Futu Quote API")
|
|
463
471
|
return True
|
|
@@ -469,7 +477,7 @@ def init_quote_connection():
|
|
|
469
477
|
|
|
470
478
|
def init_trade_connection():
|
|
471
479
|
"""Initialize trade connection only"""
|
|
472
|
-
global trade_ctx, _is_trade_initialized
|
|
480
|
+
global trade_ctx, _is_trade_initialized, _futu_host, _futu_port
|
|
473
481
|
|
|
474
482
|
if _is_trade_initialized and trade_ctx:
|
|
475
483
|
return True
|
|
@@ -489,8 +497,8 @@ def init_trade_connection():
|
|
|
489
497
|
# 创建交易上下文
|
|
490
498
|
trade_ctx = OpenSecTradeContext(
|
|
491
499
|
filter_trdmarket=trd_market,
|
|
492
|
-
host=
|
|
493
|
-
port=
|
|
500
|
+
host=_futu_host,
|
|
501
|
+
port=_futu_port,
|
|
494
502
|
security_firm=security_firm
|
|
495
503
|
)
|
|
496
504
|
|
|
@@ -554,17 +562,22 @@ def init_trade_connection():
|
|
|
554
562
|
_is_trade_initialized = False
|
|
555
563
|
return False
|
|
556
564
|
|
|
557
|
-
def init_futu_connection() -> bool:
|
|
565
|
+
def init_futu_connection(host: str = '127.0.0.1', port: int = 11111) -> bool:
|
|
558
566
|
"""
|
|
559
567
|
Initialize connection to Futu OpenD.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
host: Futu OpenD host address
|
|
571
|
+
port: Futu OpenD port number
|
|
572
|
+
|
|
560
573
|
Returns True if successful, False otherwise.
|
|
561
574
|
"""
|
|
562
|
-
global quote_ctx, trade_ctx, _is_trade_initialized
|
|
575
|
+
global quote_ctx, trade_ctx, _is_trade_initialized, _futu_host, _futu_port
|
|
563
576
|
|
|
564
577
|
try:
|
|
565
|
-
#
|
|
566
|
-
|
|
567
|
-
|
|
578
|
+
# Set global connection parameters
|
|
579
|
+
_futu_host = host
|
|
580
|
+
_futu_port = port
|
|
568
581
|
|
|
569
582
|
# Log to file only
|
|
570
583
|
logger.info(f"Initializing Futu connection to {host}:{port}")
|
|
@@ -617,10 +630,8 @@ def init_futu_connection() -> bool:
|
|
|
617
630
|
|
|
618
631
|
@asynccontextmanager
|
|
619
632
|
async def lifespan(server: Server):
|
|
620
|
-
# Startup -
|
|
621
|
-
|
|
622
|
-
logger.error("Failed to initialize quote connection")
|
|
623
|
-
raise Exception("Quote connection failed")
|
|
633
|
+
# Startup - connections are already initialized in main()
|
|
634
|
+
# No need to initialize here as it's done before mcp.run()
|
|
624
635
|
try:
|
|
625
636
|
yield
|
|
626
637
|
finally:
|
|
@@ -1742,12 +1753,15 @@ def main():
|
|
|
1742
1753
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1743
1754
|
epilog="""
|
|
1744
1755
|
Examples:
|
|
1745
|
-
futu-mcp-server
|
|
1746
|
-
futu-mcp-server --
|
|
1756
|
+
futu-mcp-server # Start the MCP server with default settings
|
|
1757
|
+
futu-mcp-server --host 192.168.1.100 --port 11111 # Connect to remote OpenD
|
|
1758
|
+
futu-mcp-server --help # Show this help message
|
|
1759
|
+
|
|
1760
|
+
Arguments:
|
|
1761
|
+
--host # Futu OpenD host (default: 127.0.0.1)
|
|
1762
|
+
--port # Futu OpenD port (default: 11111)
|
|
1747
1763
|
|
|
1748
1764
|
Environment Variables:
|
|
1749
|
-
FUTU_HOST # Futu OpenD host (default: 127.0.0.1)
|
|
1750
|
-
FUTU_PORT # Futu OpenD port (default: 11111)
|
|
1751
1765
|
FUTU_ENABLE_TRADING # Enable trading features (default: 0)
|
|
1752
1766
|
FUTU_TRADE_ENV # Trading environment: SIMULATE or REAL (default: SIMULATE)
|
|
1753
1767
|
FUTU_SECURITY_FIRM # Security firm: FUTUSECURITIES or FUTUINC (default: FUTUSECURITIES)
|
|
@@ -1756,6 +1770,19 @@ Environment Variables:
|
|
|
1756
1770
|
"""
|
|
1757
1771
|
)
|
|
1758
1772
|
|
|
1773
|
+
parser.add_argument(
|
|
1774
|
+
'--host',
|
|
1775
|
+
default='127.0.0.1',
|
|
1776
|
+
help='Futu OpenD host address (default: 127.0.0.1)'
|
|
1777
|
+
)
|
|
1778
|
+
|
|
1779
|
+
parser.add_argument(
|
|
1780
|
+
'--port',
|
|
1781
|
+
type=int,
|
|
1782
|
+
default=11111,
|
|
1783
|
+
help='Futu OpenD port number (default: 11111)'
|
|
1784
|
+
)
|
|
1785
|
+
|
|
1759
1786
|
parser.add_argument(
|
|
1760
1787
|
'--version',
|
|
1761
1788
|
action='version',
|
|
@@ -1794,13 +1821,15 @@ Environment Variables:
|
|
|
1794
1821
|
|
|
1795
1822
|
# Initialize Futu connection with file logging only
|
|
1796
1823
|
logger.info("Initializing Futu connection for MCP server...")
|
|
1797
|
-
if init_futu_connection():
|
|
1824
|
+
if init_futu_connection(args.host, args.port):
|
|
1798
1825
|
logger.info("Successfully initialized Futu connection")
|
|
1799
1826
|
logger.info("Starting MCP server in stdio mode - stdout reserved for JSON communication")
|
|
1800
1827
|
|
|
1801
1828
|
try:
|
|
1802
1829
|
# Run MCP server - stdout will be used for JSON communication only
|
|
1803
|
-
mcp.run(transport
|
|
1830
|
+
logger.info("About to call mcp.run() without transport parameter")
|
|
1831
|
+
mcp.run()
|
|
1832
|
+
logger.info("mcp.run() completed successfully")
|
|
1804
1833
|
except KeyboardInterrupt:
|
|
1805
1834
|
logger.info("Received keyboard interrupt, shutting down gracefully...")
|
|
1806
1835
|
cleanup_all()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{futu_stock_mcp_server-0.1.5 → futu_stock_mcp_server-0.1.6}/src/futu_stock_mcp_server/__init__.py
RENAMED
|
File without changes
|