# Directory Structure
```
├── .cursor
│ └── rules
│ └── akshare-cursor-rule.mdc
├── .DS_Store
├── .gitignore
├── claude_desktop_config.json
├── data
│ └── strong_stocks_20250303.csv
├── Dockerfile
├── install.sh
├── pyproject.toml
├── README.md
├── run_server.py
├── smithery.yaml
├── src
│ ├── .DS_Store
│ └── mcp_server_akshare
│ ├── __init__.py
│ ├── .DS_Store
│ ├── api.py
│ └── server.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | venv/
2 | __pycache__/
3 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # AKShare MCP Server
2 |
3 | A Model Context Protocol (MCP) server that provides financial data analysis capabilities using the AKShare library.
4 |
5 | ## Features
6 |
7 | - Access to Chinese and global financial market data through AKShare
8 | - Integration with Claude Desktop via MCP protocol
9 | - Support for various financial data queries and analysis
10 |
11 | ## Installation
12 |
13 | ### Using uv (recommended)
14 |
15 | ```bash
16 | # Clone the repository
17 | git clone https://github.com/yourusername/akshare_mcp_server.git
18 | cd akshare_mcp_server
19 |
20 | # Create and activate a virtual environment
21 | python -m venv venv
22 | source venv/bin/activate # On Windows: venv\Scripts\activate
23 |
24 | # Install dependencies with uv
25 | uv pip install -e .
26 | ```
27 |
28 | ### Using pip
29 |
30 | ```bash
31 | # Clone the repository
32 | git clone https://github.com/yourusername/akshare_mcp_server.git
33 | cd akshare_mcp_server
34 |
35 | # Create and activate a virtual environment
36 | python -m venv venv
37 | source venv/bin/activate # On Windows: venv\Scripts\activate
38 |
39 | # Install dependencies
40 | pip install -e .
41 | ```
42 |
43 | ## Usage
44 |
45 | ### Running the server
46 |
47 | ```bash
48 | # Activate the virtual environment
49 | source venv/bin/activate # On Windows: venv\Scripts\activate
50 |
51 | # Run the server
52 | python run_server.py
53 | ```
54 |
55 | ### Integrating with Claude Desktop
56 |
57 | 1. Add the following configuration to your Claude Desktop configuration:
58 |
59 | ```json
60 | "mcpServers": {
61 | "akshare-mcp": {
62 | "command": "uv",
63 | "args": [
64 | "--directory",
65 | "/path/to/akshare_mcp_server",
66 | "run",
67 | "akshare-mcp"
68 | ],
69 | "env": {
70 | "AKSHARE_API_KEY": "<your_api_key_if_needed>"
71 | }
72 | }
73 | }
74 | ```
75 |
76 | 2. Restart Claude Desktop
77 | 3. Select the AKShare MCP server from the available tools
78 |
79 | ## Available Tools
80 |
81 | The AKShare MCP server provides the following tools:
82 |
83 | - Stock data queries
84 | - Fund data queries
85 | - Bond data queries
86 | - Futures data queries
87 | - Forex data queries
88 | - Macroeconomic data queries
89 | - And more...
90 |
91 | ## Adding a New Tool
92 |
93 | To add a new tool to the MCP server, follow these steps:
94 |
95 | 1. **Add a new API function in `src/mcp_server_akshare/api.py`**:
96 | ```python
97 | async def fetch_new_data_function(param1: str, param2: str = "default") -> List[Dict[str, Any]]:
98 | """
99 | Fetch new data type.
100 |
101 | Args:
102 | param1: Description of param1
103 | param2: Description of param2
104 | """
105 | try:
106 | df = ak.akshare_function_name(param1=param1, param2=param2)
107 | return dataframe_to_dict(df)
108 | except Exception as e:
109 | logger.error(f"Error fetching new data: {e}")
110 | raise
111 | ```
112 |
113 | 2. **Add the new tool to the enum in `src/mcp_server_akshare/server.py`**:
114 | ```python
115 | class AKShareTools(str, Enum):
116 | # Existing tools...
117 | NEW_TOOL_NAME = "new_tool_name"
118 | ```
119 |
120 | 3. **Import the new function in `src/mcp_server_akshare/server.py`**:
121 | ```python
122 | from .api import (
123 | # Existing imports...
124 | fetch_new_data_function,
125 | )
126 | ```
127 |
128 | 4. **Add the tool definition to the `handle_list_tools()` function**:
129 | ```python
130 | types.Tool(
131 | name=AKShareTools.NEW_TOOL_NAME.value,
132 | description="Description of the new tool",
133 | inputSchema={
134 | "type": "object",
135 | "properties": {
136 | "param1": {"type": "string", "description": "Description of param1"},
137 | "param2": {"type": "string", "description": "Description of param2"},
138 | },
139 | "required": ["param1"], # List required parameters
140 | },
141 | ),
142 | ```
143 |
144 | 5. **Add the tool handler in the `handle_call_tool()` function**:
145 | ```python
146 | case AKShareTools.NEW_TOOL_NAME.value:
147 | param1 = arguments.get("param1")
148 | if not param1:
149 | raise ValueError("Missing required argument: param1")
150 |
151 | param2 = arguments.get("param2", "default")
152 |
153 | result = await fetch_new_data_function(
154 | param1=param1,
155 | param2=param2,
156 | )
157 | ```
158 |
159 | 6. **Test the new tool** by running the server and making a request to the new tool.
160 |
161 | ## Development
162 |
163 | ```bash
164 | # Install development dependencies
165 | uv pip install -e ".[dev]"
166 |
167 | # Run tests
168 | pytest
169 | ```
170 |
171 | ## Docker
172 |
173 | You can also run the server using Docker:
174 |
175 | ```bash
176 | # Build the Docker image
177 | docker build -t akshare-mcp-server .
178 |
179 | # Run the Docker container
180 | docker run -p 8000:8000 akshare-mcp-server
181 | ```
182 |
183 | ## License
184 |
185 | MIT
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | FROM python:3.10-slim
2 |
3 | WORKDIR /app
4 |
5 | # Install uv
6 | RUN pip install uv
7 |
8 | # Copy project files
9 | COPY . .
10 |
11 | # Install dependencies
12 | RUN uv pip install -e .
13 |
14 | # Expose port if needed
15 | # EXPOSE 8000
16 |
17 | # Run the server
18 | CMD ["python", "run_server.py"]
```
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 |
3 | # Create and activate virtual environment
4 | python3 -m venv venv
5 | source venv/bin/activate
6 |
7 | # Install uv if not already installed
8 | pip install uv
9 |
10 | # Install the package with uv
11 | uv pip install -e .
12 |
13 | echo "Installation complete. You can now run the server with 'python run_server.py'"
```
--------------------------------------------------------------------------------
/claude_desktop_config.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "mcpServers": {
3 | "akshare-mcp": {
4 | "command": "uv",
5 | "args": [
6 | "--directory",
7 | "/Users/hlchen/CodeHub/akshare_mcp_server",
8 | "run",
9 | "akshare-mcp"
10 | ],
11 | "env": {
12 | "AKSHARE_API_KEY": "<insert_api_key_if_needed>"
13 | }
14 | }
15 | }
16 | }
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | name: mcp-server-akshare
2 | version: 0.1.0
3 | description: MCP server for AKShare financial data
4 | license: MIT
5 | authors:
6 | - name: Your Name
7 | email: [email protected]
8 |
9 | dependencies:
10 | - akshare>=1.11.0
11 | - mcp>=0.1.0
12 | - httpx>=0.24.0
13 | - python-dotenv>=1.0.0
14 | - pandas>=2.0.0
15 | - numpy>=1.24.0
16 |
17 | dev-dependencies:
18 | - black>=23.3.0
19 | - isort>=5.12.0
20 | - mypy>=1.3.0
21 | - pytest>=7.3.1
22 | - pytest-asyncio>=0.21.0
23 |
24 | scripts:
25 | akshare-mcp: mcp_server_akshare:main
```
--------------------------------------------------------------------------------
/run_server.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python
2 | """
3 | Entry point for the AKShare MCP server.
4 | """
5 |
6 | import asyncio
7 | import logging
8 | import sys
9 |
10 | from src.mcp_server_akshare import main
11 |
12 | # Configure logging
13 | logging.basicConfig(
14 | level=logging.INFO,
15 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
16 | )
17 | logger = logging.getLogger(__name__)
18 |
19 | if __name__ == "__main__":
20 | print("Starting AKShare MCP server...")
21 | try:
22 | asyncio.run(main())
23 | except KeyboardInterrupt:
24 | print("Server stopped by user.")
25 | sys.exit(0)
26 | except Exception as e:
27 | logger.error(f"Error running server: {e}", exc_info=True)
28 | sys.exit(1)
```
--------------------------------------------------------------------------------
/src/mcp_server_akshare/__init__.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | MCP server for AKShare financial data.
3 | """
4 |
5 | import asyncio
6 | import logging
7 | from typing import Optional
8 |
9 | from .server import main as server_main
10 |
11 | # Configure logging
12 | logging.basicConfig(
13 | level=logging.INFO,
14 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
15 | )
16 | logger = logging.getLogger(__name__)
17 |
18 |
19 | async def main() -> None:
20 | """
21 | Main entry point for the AKShare MCP server.
22 | """
23 | logger.info("Starting AKShare MCP server...")
24 | try:
25 | await server_main()
26 | except Exception as e:
27 | logger.error(f"Error running AKShare MCP server: {e}", exc_info=True)
28 | raise
29 |
30 |
31 | if __name__ == "__main__":
32 | asyncio.run(main())
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "mcp-server-akshare"
7 | version = "0.1.0"
8 | description = "MCP server for AKShare financial data"
9 | readme = "README.md"
10 | requires-python = ">=3.10"
11 | license = {text = "MIT"}
12 | authors = [
13 | {name = "Your Name", email = "[email protected]"},
14 | ]
15 | dependencies = [
16 | "akshare>=1.11.0",
17 | "mcp>=0.1.0",
18 | "httpx>=0.24.0",
19 | "python-dotenv>=1.0.0",
20 | "pandas>=2.0.0",
21 | "numpy>=1.24.0",
22 | ]
23 |
24 | [project.optional-dependencies]
25 | dev = [
26 | "black>=23.3.0",
27 | "isort>=5.12.0",
28 | "mypy>=1.3.0",
29 | "pytest>=7.3.1",
30 | "pytest-asyncio>=0.21.0",
31 | ]
32 |
33 | [project.scripts]
34 | akshare-mcp = "mcp_server_akshare:main"
35 |
36 | [tool.black]
37 | line-length = 100
38 | target-version = ["py310"]
39 |
40 | [tool.isort]
41 | profile = "black"
42 | line_length = 100
43 |
44 | [tool.mypy]
45 | python_version = "3.10"
46 | warn_return_any = true
47 | warn_unused_configs = true
48 | disallow_untyped_defs = true
49 | disallow_incomplete_defs = true
```
--------------------------------------------------------------------------------
/src/mcp_server_akshare/api.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | API functions for interacting with AKShare.
3 | """
4 |
5 | import logging
6 | import os
7 | from typing import Any, Dict, List, Optional, Union
8 |
9 | import akshare as ak
10 | import pandas as pd
11 | from dotenv import load_dotenv
12 |
13 | # Load environment variables
14 | load_dotenv()
15 |
16 | # Configure logging
17 | logger = logging.getLogger(__name__)
18 |
19 | # Optional API key (if needed for certain endpoints)
20 | API_KEY = os.getenv("AKSHARE_API_KEY")
21 |
22 |
23 | def dataframe_to_dict(df: pd.DataFrame) -> List[Dict[str, Any]]:
24 | """
25 | Convert a pandas DataFrame to a list of dictionaries.
26 | """
27 | return df.to_dict(orient="records")
28 |
29 |
30 | def dataframe_to_json(df: pd.DataFrame) -> str:
31 | """
32 | Convert a pandas DataFrame to a JSON string.
33 | """
34 | return df.to_json(orient="records", date_format="iso")
35 |
36 |
37 | async def fetch_stock_zh_a_spot() -> List[Dict[str, Any]]:
38 | """
39 | Fetch A-share stock data.
40 | """
41 | try:
42 | df = ak.stock_zh_a_spot()
43 | return dataframe_to_dict(df)
44 | except Exception as e:
45 | logger.error(f"Error fetching A-share stock data: {e}")
46 | raise
47 |
48 |
49 | async def fetch_stock_zh_a_hist(
50 | symbol: str,
51 | period: str = "daily",
52 | start_date: str = None,
53 | end_date: str = None,
54 | adjust: str = ""
55 | ) -> List[Dict[str, Any]]:
56 | """
57 | Fetch A-share stock historical data.
58 |
59 | Args:
60 | symbol: Stock code
61 | period: Data frequency, options: daily, weekly, monthly
62 | start_date: Start date in format YYYYMMDD
63 | end_date: End date in format YYYYMMDD
64 | adjust: Price adjustment, options: "", qfq (forward), hfq (backward)
65 | """
66 | try:
67 | df = ak.stock_zh_a_hist(
68 | symbol=symbol,
69 | period=period,
70 | start_date=start_date,
71 | end_date=end_date,
72 | adjust=adjust
73 | )
74 | return dataframe_to_dict(df)
75 | except Exception as e:
76 | logger.error(f"Error fetching stock historical data for {symbol}: {e}")
77 | raise
78 |
79 |
80 | async def fetch_stock_zh_index_spot() -> List[Dict[str, Any]]:
81 | """
82 | Fetch Chinese stock market index data.
83 | """
84 | try:
85 | df = ak.stock_zh_index_spot()
86 | return dataframe_to_dict(df)
87 | except Exception as e:
88 | logger.error(f"Error fetching stock index data: {e}")
89 | raise
90 |
91 |
92 | async def fetch_stock_zh_index_daily(symbol: str) -> List[Dict[str, Any]]:
93 | """
94 | Fetch Chinese stock market index daily data.
95 |
96 | Args:
97 | symbol: Index code
98 | """
99 | try:
100 | df = ak.stock_zh_index_daily(symbol=symbol)
101 | return dataframe_to_dict(df)
102 | except Exception as e:
103 | logger.error(f"Error fetching stock index daily data for {symbol}: {e}")
104 | raise
105 |
106 |
107 | async def fetch_fund_etf_category_sina(category: str = "ETF基金") -> List[Dict[str, Any]]:
108 | """
109 | Fetch ETF fund data from Sina.
110 |
111 | Args:
112 | category: Fund category
113 | """
114 | try:
115 | df = ak.fund_etf_category_sina(category=category)
116 | return dataframe_to_dict(df)
117 | except Exception as e:
118 | logger.error(f"Error fetching ETF fund data: {e}")
119 | raise
120 |
121 |
122 | async def fetch_fund_etf_hist_sina(symbol: str) -> List[Dict[str, Any]]:
123 | """
124 | Fetch ETF fund historical data from Sina.
125 |
126 | Args:
127 | symbol: ETF fund code
128 | """
129 | try:
130 | df = ak.fund_etf_hist_sina(symbol=symbol)
131 | return dataframe_to_dict(df)
132 | except Exception as e:
133 | logger.error(f"Error fetching ETF fund historical data for {symbol}: {e}")
134 | raise
135 |
136 |
137 | async def fetch_macro_china_gdp() -> List[Dict[str, Any]]:
138 | """
139 | Fetch China GDP data.
140 | """
141 | try:
142 | df = ak.macro_china_gdp()
143 | return dataframe_to_dict(df)
144 | except Exception as e:
145 | logger.error(f"Error fetching China GDP data: {e}")
146 | raise
147 |
148 |
149 | async def fetch_macro_china_cpi() -> List[Dict[str, Any]]:
150 | """
151 | Fetch China CPI data.
152 | """
153 | try:
154 | df = ak.macro_china_cpi()
155 | return dataframe_to_dict(df)
156 | except Exception as e:
157 | logger.error(f"Error fetching China CPI data: {e}")
158 | raise
159 |
160 |
161 | async def fetch_forex_spot_quote() -> List[Dict[str, Any]]:
162 | """
163 | Fetch forex spot quotes.
164 | """
165 | try:
166 | df = ak.forex_spot_quote()
167 | return dataframe_to_dict(df)
168 | except Exception as e:
169 | logger.error(f"Error fetching forex spot quotes: {e}")
170 | raise
171 |
172 |
173 | async def fetch_futures_zh_spot() -> List[Dict[str, Any]]:
174 | """
175 | Fetch Chinese futures market spot data.
176 | """
177 | try:
178 | df = ak.futures_zh_spot()
179 | return dataframe_to_dict(df)
180 | except Exception as e:
181 | logger.error(f"Error fetching futures spot data: {e}")
182 | raise
183 |
184 |
185 | async def fetch_bond_zh_hs_cov_spot() -> List[Dict[str, Any]]:
186 | """
187 | Fetch Chinese convertible bond data.
188 | """
189 | try:
190 | df = ak.bond_zh_hs_cov_spot()
191 | return dataframe_to_dict(df)
192 | except Exception as e:
193 | logger.error(f"Error fetching convertible bond data: {e}")
194 | raise
195 |
196 |
197 | async def fetch_stock_zt_pool_strong_em(date: str = None) -> List[Dict[str, Any]]:
198 | """
199 | Fetch strong stock pool data from East Money.
200 |
201 | Args:
202 | date: Date in format YYYYMMDD
203 | """
204 | try:
205 | logger.info(f"Fetching strong stock pool data for date: {date}")
206 | df = ak.stock_zt_pool_strong_em(date=date)
207 |
208 | logger.info(f"Result type: {type(df)}")
209 | logger.info(f"Is DataFrame empty: {df.empty}")
210 |
211 | if not df.empty:
212 | logger.info(f"DataFrame shape: {df.shape}")
213 | logger.info(f"DataFrame columns: {df.columns.tolist()}")
214 | return dataframe_to_dict(df)
215 | else:
216 | logger.warning(f"No data available for date: {date}")
217 |
218 | # Try without date parameter as a fallback
219 | if date:
220 | logger.info("Trying again without date parameter as fallback...")
221 | df_fallback = ak.stock_zt_pool_strong_em()
222 | logger.info(f"Fallback result type: {type(df_fallback)}")
223 | logger.info(f"Fallback is DataFrame empty: {df_fallback.empty}")
224 |
225 | if not df_fallback.empty:
226 | logger.info(f"Fallback DataFrame shape: {df_fallback.shape}")
227 | logger.info(f"Fallback DataFrame columns: {df_fallback.columns.tolist()}")
228 | return dataframe_to_dict(df_fallback)
229 | else:
230 | logger.warning("Fallback also returned empty DataFrame")
231 |
232 | # Return empty list if no data is available
233 | return []
234 | except Exception as e:
235 | logger.error(f"Error fetching strong stock pool data for date {date}: {e}")
236 | raise
```