Skip to main content
Glama
ariesanhthu

VNStock MCP Server

by ariesanhthu
server.py26.9 kB
from vnstock import Quote from vnstock.explorer.tcbs.company import Company as TCBSCompany from vnstock.explorer.vci.company import Company as VCICompany from vnstock.explorer.vci.listing import Listing as VCIListing from vnstock.explorer.vci.financial import Finance as VCIFinance from vnstock.explorer.fmarket.fund import Fund as FMarketFund from vnstock.explorer.misc.gold_price import sjc_gold_price, btmc_goldprice from vnstock.explorer.misc.exchange_rate import vcb_exchange_rate from vnstock.explorer.vci.trading import Trading as VCITrading from mcp.server.fastmcp import FastMCP import pandas as pd from typing import Literal from datetime import datetime import argparse import sys import os server = FastMCP("VNStock MCP Server") ##### Company Tools ##### @server.tool() def get_company_overview( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get company overview from stock market Args: symbol: str output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.overview() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_news( symbol: str, page_size: int = 10, page: int = 0, output_format: Literal["json", "dataframe"] = "json", ): """ Get company news from stock market Args: symbol: str page_size: int = 10 page: int = 0 output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.news(page_size=page_size, page=page) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_events( symbol: str, page_size: int = 10, page: int = 0, output_format: Literal["json", "dataframe"] = "json", ): """ Get company events from stock market Args: symbol: str page_size: int = 10 page: int = 0 output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.events(page_size=page_size, page=page) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_shareholders( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get company shareholders from stock market Args: symbol: str output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.shareholders() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_officers( symbol: str, filter_by: Literal["working", "all", "resigned"] = "working", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] # noqa: E501 """ Get company officers from stock market Args: symbol: str filter_by: Literal['working', "all", 'resigned'] = 'working' output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.officers(filter_by=filter_by) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_subsidiaries( symbol: str, filter_by: Literal["all", "subsidiary"] = "all", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] """ Get company subsidiaries from stock market Args: symbol: str filter_by: Literal["all", "subsidiary"] = "all" output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.subsidiaries(filter_by=filter_by) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_reports( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get company reports from stock market Args: symbol: str output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = VCICompany(symbol=symbol) df = equity.reports() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_dividends( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get company dividends from stock market Args: symbol: str output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.dividends() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_insider_deals( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get company insider deals from stock market Args: symbol: str output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = TCBSCompany(symbol=symbol) df = equity.insider_deals() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_ratio_summary( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get company ratio summary from stock market Args: symbol: str output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = VCICompany(symbol=symbol) df = equity.ratio_summary() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_company_trading_stats( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get company trading stats from stock market Args: symbol: str output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ equity = VCICompany(symbol=symbol) df = equity.trading_stats() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df ##### Listing Tools ##### @server.tool() def get_all_symbol_groups(output_format: Literal["json", "dataframe"] = "json"): """ Get all symbol groups from stock market Args: output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ df = pd.DataFrame( [ {"group": "HOSE", "group_name": "All symbols in HOSE"}, {"group": "HNX", "group_name": "All symbols in HNX"}, {"group": "UPCOM", "group_name": "All symbols in UPCOM"}, {"group": "VN30", "group_name": "All symbols in VN30"}, {"group": "VN100", "group_name": "All symbols in VN100"}, {"group": "HNX30", "group_name": "All symbols in HNX30"}, {"group": "VNMidCap", "group_name": "All symbols in VNMidCap"}, {"group": "VNSmallCap", "group_name": "All symbols in VNSmallCap"}, {"group": "VNAllShare", "group_name": "All symbols in VNAllShare"}, {"group": "HNXCon", "group_name": "All symbols in HNXCon"}, {"group": "HNXFin", "group_name": "All symbols in HNXFin"}, {"group": "HNXLCap", "group_name": "All symbols in HNXLCap"}, {"group": "HNXMSCap", "group_name": "All symbols in HNXMSCap"}, {"group": "HNXMan", "group_name": "All symbols in HNXMan"}, {"group": "ETF", "group_name": "All symbols in ETF"}, {"group": "FU_INDEX", "group_name": "All symbols in FU_INDEX"}, {"group": "CW", "group_name": "All symbols in CW"}, ] ) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_all_industries(output_format: Literal["json", "dataframe"] = "json"): """ Get all symbols from stock market Args: output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame or json """ listing = VCIListing() df = listing.industries_icb() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_all_symbols_by_group( group: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get all symbols from stock market Args: group: str (group name to get symbols) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ listing = VCIListing() df = listing.symbols_by_group(group=group) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_all_symbols_by_industry( industry: str = None, output_format: Literal["json", "dataframe"] = "json" ): """ Get all symbols from stock market Args: industry: str = None (if None, return all symbols) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame or json """ listing = VCIListing() df = listing.symbols_by_industries() if industry: codes = ["icb_code1", "icb_code2", "icb_code3", "icb_code4"] masks = [] for col in codes: if col in df.columns: masks.append(df[col].astype(str) == industry) if masks: mask = masks[0] for m in masks[1:]: mask = mask | m df = df[mask] if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_all_symbols(output_format: Literal["json", "dataframe"] = "json"): """ Get all symbols from stock market Args: output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame or json """ listing = VCIListing() df = listing.symbols_by_exchange() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df ##### Finance Tools ##### @server.tool() def get_income_statements( symbol: str, period: Literal["quarter", "year"] = "year", output_format: Literal["json", "dataframe"] = "json", ): """ Get income statements of a company from stock market Args: symbol: str (symbol of the company to get income statements) period: Literal['quarter', 'year'] = 'year' (period to get income statements) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ finance = VCIFinance(symbol=symbol, period=period) df = finance.income_statement() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_balance_sheets( symbol: str, period: Literal["quarter", "year"] = "year", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] """ Get balance sheets of a company from stock market Args: symbol: str (symbol of the company to get balance sheets) period: Literal['quarter', 'year'] = 'year' (period to get balance sheets) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ finance = VCIFinance(symbol=symbol, period=period) df = finance.balance_sheet() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_cash_flows( symbol: str, period: Literal["quarter", "year"] = "year", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] """ Get cash flows of a company from stock market Args: symbol: str (symbol of the company to get cash flows) period: Literal['quarter', 'year'] = 'year' (period to get cash flows) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ finance = VCIFinance(symbol=symbol, period=period) df = finance.cash_flow() return df @server.tool() def get_finance_ratios( symbol: str, period: Literal["quarter", "year"] = "year", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] """ Get finance ratios of a company from stock market Args: symbol: str (symbol of the company to get finance ratios) period: Literal['quarter', 'year'] = 'year' (period to get finance ratios) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ finance = VCIFinance(symbol=symbol, period=period) df = finance.ratio() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_raw_report( symbol: str, period: Literal["quarter", "year"] = "year", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] """ Get raw report of a company from stock market Args: symbol: str (symbol of the company to get raw report) period: Literal['quarter', 'year'] = 'year' (period to get raw report) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ finance = VCIFinance(symbol=symbol, period=period) df = finance._get_report(mode="raw") if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df ##### Fund Tools ##### @server.tool() def list_all_funds( fund_type: Literal["BALANCED", "BOND", "STOCK", None] = None, output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] """ List all funds from stock market Args: fund_type: Literal['BALANCED', 'BOND', 'STOCK', None ] = None (if None, return funds in all types) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ fund = FMarketFund() df = fund.listing(fund_type=fund_type) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def search_fund(keyword: str, output_format: Literal["json", "dataframe"] = "json"): """ Search fund by name from stock market Args: keyword: str (partial match for fund name to search) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ fund = FMarketFund() df = fund.filter(symbol=keyword) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_fund_nav_report( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get nav report of a fund from stock market Args: symbol: str (symbol of the fund to get nav report) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ fund = FMarketFund() df = fund.details.nav_report(symbol=symbol) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_fund_top_holding( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get top holding of a fund from stock market Args: symbol: str (symbol of the fund to get top holding) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ fund = FMarketFund() df = fund.details.top_holding(symbol=symbol) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_fund_industry_holding( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get industry holding of a fund from stock market Args: symbol: str (symbol of the fund to get industry holding) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ fund = FMarketFund() df = fund.details.industry_holding(symbol=symbol) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_fund_asset_holding( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get asset holding of a fund from stock market Args: symbol: str (symbol of the fund to get asset holding) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ fund = FMarketFund() df = fund.details.asset_holding(symbol=symbol) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df ##### MISC Tools ##### @server.tool() def get_gold_price( date: str = None, source: Literal["SJC", "BTMC"] = "SJC", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] # noqa: F821 """ Get gold price from stock market Args: date: str = None (if None, return today's price. Format: YYYY-MM-DD) source: Literal['SJC', 'BTMC'] = 'SJC' (source to get gold price) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ if date: price = sjc_gold_price(date=date) if output_format == "json": return price.to_json(orient="records", force_ascii=False) else: return price else: price = sjc_gold_price() if source == "SJC" else btmc_goldprice() if output_format == "json": return price.to_json(orient="records", force_ascii=False) else: return price @server.tool() def get_exchange_rate( date: str = None, output_format: Literal["json", "dataframe"] = "json" ): """ Get exchange rate of all currency pairs from stock market Args: date: str = None (if None, return today's price. Format: YYYY-MM-DD) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ if not date: date = datetime.now().strftime("%Y-%m-%d") price = vcb_exchange_rate(date=date) if output_format == "json": return price.to_json(orient="records", force_ascii=False) else: return price ##### Quote Tools ##### @server.tool() def get_quote_history_price( symbol: str, start_date: str, end_date: str = None, interval: Literal["1m", "5m", "15m", "30m", "1H", "1D", "1W", "1M"] = "1D", output_format: Literal["json", "dataframe"] = "json", ): # pyright: ignore[reportUndefinedVariable] # noqa: F722 """ Get quote price history of a symbol from stock market Args: symbol: str (symbol to get history price) start_date: str (format: YYYY-MM-DD) end_date: str = None (end date to get history price. None means today) interval: Literal['1m', '5m', '15m', '30m', '1H', '1D', '1W', '1M'] = '1D' (interval to get history price) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ quote = Quote(symbol=symbol, source="VCI") df = quote.history( start_date=start_date, end_date=end_date or datetime.now().strftime("%Y-%m-%d"), interval=interval, ) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_quote_intraday_price( symbol: str, page_size: int = 100, last_time: str = None, output_format: Literal["json", "dataframe"] = "json", ): """ Get quote intraday price from stock market Args: symbol: str (symbol to get intraday price) page_size: int = 500 (max: 100000) (number of rows to return) last_time: str = None (last time to get intraday price from) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ quote = Quote(symbol=symbol, source="VCI") df = quote.intraday(page_size=page_size, last_time=last_time) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df @server.tool() def get_quote_price_depth( symbol: str, output_format: Literal["json", "dataframe"] = "json" ): """ Get quote price depth from stock market Args: symbol: str (symbol to get price depth) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ quote = Quote(symbol=symbol, source="VCI") df = quote.price_depth() if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df ##### Trading Tools ##### @server.tool() def get_price_board( symbols: list[str], output_format: Literal["json", "dataframe"] = "json" ): """ Get price board from stock market Args: symbols: list[str] (list of symbols to get price board) output_format: Literal['json', 'dataframe'] = 'json' Returns: pd.DataFrame """ trading = VCITrading() df = trading.price_board(symbols_list=symbols) if output_format == "json": return df.to_json(orient="records", force_ascii=False) else: return df def main(): """Main entry point for the vnstock-mcp-server CLI.""" parser = argparse.ArgumentParser( description="VNStock MCP Server - Vietnam Stock Market Data Access via MCP", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: %(prog)s # Run with default stdio transport %(prog)s --transport stdio # Explicitly use stdio transport %(prog)s --transport sse # Use Server-Sent Events transport %(prog)s --transport sse --mount-path /vnstock # SSE with custom mount path %(prog)s --transport streamable-http # Use HTTP streaming transport Transport Modes: stdio : Standard input/output (default, for MCP clients like Claude Desktop) sse : Server-Sent Events (for web applications) streamable-http: HTTP streaming (for HTTP-based integrations) """, ) parser.add_argument( "--transport", "-t", choices=["stdio", "sse", "streamable-http"], default="stdio", help="Transport protocol to use (default: stdio)", ) parser.add_argument( "--mount-path", "-m", type=str, default=None, help="Mount path for SSE transport (optional, used only with --transport sse)", ) parser.add_argument("--version", "-v", action="version", version="%(prog)s 1.0.0") try: args = parser.parse_args() # Validate arguments if args.transport == "sse" and args.mount_path is None: print( "Warning: Using SSE transport without mount-path. Default mount path will be used.", file=sys.stderr, ) if args.transport != "sse" and args.mount_path is not None: print( "Warning: --mount-path is only used with SSE transport. Ignoring mount-path.", file=sys.stderr, ) # Get host and port from environment variables (for cloud deployment) # Default to 0.0.0.0 for cloud compatibility (Render, etc.) host = os.environ.get("HOST", "0.0.0.0") port = int(os.environ.get("PORT", 8000)) # Run server with specified transport print( f"Starting VNStock MCP Server with {args.transport} transport on {host}:{port}...", file=sys.stderr, ) if args.transport == "sse" and args.mount_path: print(f"SSE mount path: {args.mount_path}", file=sys.stderr) # FastMCP uses uvicorn internally, we need to patch uvicorn to use host/port # For streamable-http and sse transports, patch uvicorn at multiple levels if args.transport in ["sse", "streamable-http"]: import uvicorn from uvicorn.config import Config from uvicorn.server import Server # Patch uvicorn.run() function original_uvicorn_run = uvicorn.run def patched_uvicorn_run(app, **kwargs): kwargs["host"] = host kwargs["port"] = port return original_uvicorn_run(app, **kwargs) uvicorn.run = patched_uvicorn_run # Patch Config.__init__ to force host and port original_config_init = Config.__init__ def patched_config_init(self, app, **kwargs): kwargs["host"] = host kwargs["port"] = port return original_config_init(self, app, **kwargs) Config.__init__ = patched_config_init # Also patch Server.__init__ as backup original_server_init = Server.__init__ def patched_server_init(self, config, **kwargs): if hasattr(config, "host"): config.host = host if hasattr(config, "port"): config.port = port return original_server_init(self, config, **kwargs) Server.__init__ = patched_server_init server.run(transport=args.transport, mount_path=args.mount_path) except KeyboardInterrupt: print("\nServer stopped by user.", file=sys.stderr) sys.exit(0) except Exception as e: print(f"Error starting server: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ariesanhthu/mcp-server-vietnam-stock-trading'

If you have feedback or need assistance with the MCP directory API, please join our Discord server