文将详细介绍如何利用DeepSeek大模型、MCP协议和AKShare金融数据接口,构建一个能够理解用户金融问题并自动获取相关数据回答的智能系统。
提纲
-
技术栈概述与准备工作
-
DeepSeek大模型介绍与API申请
-
MCP协议原理与架构解析
-
AKShare金融数据接口简介
-
Python 3.11环境配置
-
-
系统架构设计
-
整体架构图与数据流
-
MCP Server与Client的角色分配
-
DeepSeek作为核心推理引擎的集成方式
-
-
AKShare MCP Server实现
-
金融数据接口封装设计
-
使用FastMCP创建MCP服务
-
支持的主要金融数据功能
-
-
DeepSeek与MCP集成
-
配置DeepSeek支持Function Calling
-
构建MCP Client调用链
-
使用LangChain实现智能路由
-
-
完整问答流程实现
-
用户问题解析与意图识别
-
MCP工具选择与参数生成
-
数据获取与结果格式化
-
对话历史管理
-
-
部署与优化
-
本地开发环境部署
-
性能优化技巧
-
错误处理与日志记录
-
-
实际应用案例展示
-
股票数据查询示例
-
宏观经济指标分析
-
投资组合建议生成
-
1. 技术栈概述与准备工作
DeepSeek大模型介绍与API申请
DeepSeek是由深度求索公司开发的大规模语言模型,最新版本DeepSeek-V3采用混合专家(MoE)架构,拥有671B参数,激活37B参数,在多项基准测试中表现优异3。要使用DeepSeek的API服务:
-
访问DeepSeek平台注册账号
-
获取API Key(形如
sk-e508ba616396*****2c1ee7b17
)
MCP协议原理与架构解析
MCP(Model Context Protocol)是由Anthropic提出的标准化协议,它如同AI领域的"USB-C接口",统一了不同大模型与外部工具的交互方式14。MCP的核心组件包括:
-
MCP Server:提供具体功能的节点,如我们的AKShare金融数据服务
-
MCP Client:负责连接大模型与MCP Server的中间件
-
传输层:支持Stdio(本地进程通信)和SSE(网络通信)等方式5
MCP解决了传统Function Calling的平台依赖问题,使开发者可以"一次开发,多模型通用"1。
AKShare金融数据接口简介
AKShare是基于Python的金融数据接口库,提供股票、基金、期货、宏观经济等领域的海量数据。主要特点包括:
-
免费开源,数据源丰富
-
支持多种数据格式(DataFrame/JSON)
-
持续更新维护
安装命令
python
pip install akshare
pip install langchain langgraph langchain-mcp-adapters langchain-deepseek akshare fastapi uvicorn
2. 流程设计
-
用户提出问题,如"腾讯控股最近一周股价走势如何?"
-
DeepSeek分析问题,判断需要调用金融数据接口
-
MCP Client根据DeepSeek的指示调用对应的AKShare MCP Server
-
AKShare获取数据后返回给MCP Client
-
DeepSeek整合数据生成最终回答
3. AKShare MCP Server实现
我们将创建一个专门处理金融数据的MCP Server,核心代码如下:
python
from mcp.server.fastmcp import FastMCP
import akshare as ak
import logging
from typing import List, Dict, Any
from datetime import datetime, timedelta
import pandas as pd
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
mcp = FastMCP("AKShareFinance")
@mcp.tool()
def get_stock_hist(symbol: str, start_date: str, end_date: str) -> List[Dict[str, Any]]:
"""
获取股票历史数据
:param symbol: 股票代码,如'00700'(腾讯控股)
:param start_date: 开始日期,格式'YYYY-MM-DD'
:param end_date: 结束日期,格式'YYYY-MM-DD'
:return: 包含历史数据的字典列表
"""
logger.info(f"Fetching stock history for {symbol} from {start_date} to {end_date}")
df = ak.stock_hk_hist(symbol=symbol, start_date=start_date, end_date=end_date)
return df.to_dict('records')
@mcp.tool()
def get_stock_info(symbol: str) -> Dict[str, Any]:
"""
获取股票基本信息
:param symbol: 股票代码
:return: 包含股票基本信息的字典
"""
logger.info(f"Fetching stock info for {symbol}")
df = ak.stock_hk_spot()
stock_info = df[df['代码'] == symbol].iloc[0].to_dict()
return stock_info
@mcp.tool()
def get_macro_economic_data(indicator: str) -> List[Dict[str, Any]]:
"""
获取宏观经济数据
:param indicator: 指标名称,如'GDP'、'CPI'等
:return: 包含宏观经济数据的字典列表
"""
indicator_map = {
'GDP': ak.macro_china_gdp,
'CPI': ak.macro_china_cpi,
'PMI': ak.macro_china_pmi
}
if indicator not in indicator_map:
raise ValueError(f"Unsupported indicator: {indicator}")
logger.info(f"Fetching macroeconomic data for {indicator}")
df = indicator_map[indicator]()
return df.to_dict('records')
if __name__ == "__main__":
logger.info("Starting AKShare Finance MCP Server")
mcp.run(transport="sse", port=8000)
此MCP Server提供了三个核心功能:
-
获取港股历史数据
-
获取股票实时信息
-
获取宏观经济指标数据
4. DeepSeek与MCP集成
配置DeepSeek支持Function Calling
虽然DeepSeek原生不完全支持Function Calling,但我们可以通过以下方式实现:
-
使用LangChain的DeepSeek封装
-
配置MCP适配器作为Function Calling的桥梁
python
from langchain_deepseek import ChatDeepSeek
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
# 初始化DeepSeek
llm = ChatDeepSeek(
model="deepseek-chat",
api_key="your_api_key_here",
temperature=0.3
)
# 配置MCP Client
mcp_client = MultiServerMCPClient(
servers={
"finance": "http://localhost:8000" # AKShare MCP Server地址
}
)
# 创建React Agent
agent = create_react_agent(llm, mcp_client)
构建MCP工具描述
为了让DeepSeek知道何时以及如何调用我们的金融数据工具,需要提供清晰的工具描述:
python
{
"name": "get_stock_hist",
"description": "获取指定股票在特定时间范围内的历史交易数据,包括开盘价、收盘价、最高价、最低价和成交量等信息。",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "股票代码,如腾讯控股是'00700'"
},
"start_date": {
"type": "string",
"description": "开始日期,格式为YYYY-MM-DD"
},
"end_date": {
"type": "string",
"description": "结束日期,格式为YYYY-MM-DD"
}
},
"required": ["symbol", "start_date", "end_date"]
}
}
5. 完整问答流程实现
用户问题解析与意图识别
当用户提问"腾讯控股过去一周的股价表现如何?"时,系统处理流程:
-
DeepSeek分析问题,识别关键信息:
-
股票名称:腾讯控股 → 代码00700
-
时间范围:过去一周 → 计算起止日期
-
-
生成函数调用参数:
{
"symbol": "00700",
"start_date": "2025-04-13",
"end_date": "2025-04-20"
}
3.通过MCP Client调用AKShare服务获取数据
数据获取与结果格式化
获取到的原始数据示例:
python
[
{'日期': '2025-04-13', '开盘': 320.0, '收盘': 325.0, '最高': 328.0, '最低': 319.0, '成交量': 1500000},
{'日期': '2025-04-14', '开盘': 326.0, '收盘': 322.0, '最高': 327.0, '最低': 321.0, '成交量': 1200000},
...
]
·
DeepSeek会分析这些数据并生成自然语言回答:
"腾讯控股(00700)在过去一周(4月13日至4月20日)的股价表现如下:周初开盘320港元,最高触及328港元,周末收盘报325港元,整体呈现小幅上涨趋势..."
对话历史管理
使用LangChain的ConversationBufferMemory维护对话上下文:
python
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
memory.save_context(
{"input": "腾讯控股过去一周的股价表现如何?"},
{"output": "腾讯控股(00700)在过去一周..."}
)
# 后续问题可以引用上下文
response = agent.run("那成交量如何?", memory=memory)
7. 实际应用案例展示
案例展示的服务器使用的是腾讯 cloud studio 提供的免费的高性能工作空间,非常好的免费GPU算力平台。
登录后选择模板直接生成一个工作空间。

一、创建mcp的sse服务器端代码
python
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from mcp.server.sse import SseServerTransport
from starlette.requests import Request
from starlette.routing import Mount, Route
from mcp.server import Server
import uvicorn
import akshare as ak
import json
from datetime import datetime
from dotenv import load_dotenv
import os
load_dotenv() # load environment variables from .env
# 初始化 MCP 服务器
mcp = FastMCP("StockServer")
@mcp.tool()
def stock_bid_ask_em(stock_code: str) -> str:
"""获取指定股票代码的行情信息,单次返回指定股票的行情报价数据。
获取 A 股股票的实时行情数据,包括股票名称、最新价格和涨跌幅信息。
Args:
stock_code (str): 股票代码,例如 symbol="000001";
Returns:
str: 返回包含股票行情信息的字符串,
Raises:
Exception: 当查询股票数据发生异常时抛出
"""
try:
stock_bid_ask_em_df = ak.stock_bid_ask_em(symbol=stock_code)
if stock_bid_ask_em_df.empty:
return "未找到该股票的行情信息。"
else:
return stock_bid_ask_em_df.to_json(orient='records', force_ascii=False)
except Exception as e:
return f"查询股票行情时出现错误: {e}"
@mcp.tool()
def stock_zh_a_hist(stock_code: str,
start_date: str =datetime.now().strftime("%Y-%m-%d") ,
end_date: str = datetime.now().strftime("%Y-%m-%d")) -> str:
""" 沪深京 A 股日频率数据; 历史数据按日频率更新, 当日收盘价请在收盘后获取
Args:
stock_code (str): 股票代码,例如 symbol="000001";
start_date(str): 开始查询的日期,例如start_date='20210301';
end_date(str): 结束查询的日期,例如end_date ='20210616';
Returns:
str: 返回包含股票行情信息的字符串,
Raises:
Exception: 当查询股票数据发生异常时抛出
"""
try:
stock_zh_a_hist_df = ak.stock_zh_a_hist(symbol=stock_code, period="daily", start_date=start_date, end_date=end_date, adjust="hfq")
if stock_zh_a_hist_df.empty:
return "未找到该股票的行情信息。"
else:
return stock_zh_a_hist_df.to_json(orient='records', force_ascii=False)
except Exception as e:
return f"查询股票行情时出现错误: {e}"
@mcp.tool()
def stock_news_em(stock_code: str) -> str:
""" 指定个股的新闻资讯数据
Args:
stock_code (str): 股票代码,例如 symbol="000001";
Returns:
str: 指定 symbol 当日最近 100 条新闻资讯数据,
Raises:
Exception: 当查询股票数据发生异常时抛出
"""
try:
stock_news_em_df = ak.stock_news_em(symbol=stock_code)
if stock_news_em_df.empty:
return "未找到该股票的信息。"
else:
return stock_news_em_df.to_json(orient='records', force_ascii=False)
except Exception as e:
return f"查询股票信息时出现错误: {e}"
@mcp.tool()
def stock_news_main_cx():
""" 财新数据通-股票精选新闻内容
Returns:
str: 返回所有历史新闻数据
Raises:
Exception: 当查询股票数据发生异常时抛出
"""
try:
stock_news_main_cx_df = ak.stock_news_main_cx()
if stock_news_main_cx_df.empty:
return "未找到该股票的信息。"
else:
return stock_news_main_cx_df.head(20).to_json(orient='records', force_ascii=False)
except Exception as e:
return f"查询股票信息时出现错误: {e}"
def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette:
sse = SseServerTransport("/messages/")
async def handle_sse(request: Request) -> None:
async with sse.connect_sse(
request.scope,
request.receive,
request._send, # noqa: SLF001
) as (read_stream, write_stream):
await mcp_server.run(
read_stream,
write_stream,
mcp_server.create_initialization_options(),
)
return Starlette(
debug=debug,
routes=[
Route("/sse", endpoint=handle_sse),
Mount("/messages/", app=sse.handle_post_message),
],
)
if __name__ == "__main__":
mcp_server = mcp._mcp_server # noqa: WPS437
import argparse
parser = argparse.ArgumentParser(description='Run MCP SSE-based server')
parser.add_argument('--host', default='0.0.0.0', help='Host to bind to')
parser.add_argument('--port', type=int, default=9000, help='Port to listen on')
args = parser.parse_args()
# Bind SSE request handling to MCP server
starlette_app = create_starlette_app(mcp_server, debug=True)
uvicorn.run(starlette_app, host=args.host, port=args.port)
二、创建mcp的sse客户端代码
python
import asyncio
from typing import Optional
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.client.sse import sse_client
from dotenv import load_dotenv
import os
load_dotenv() # load environment variables from .env
from openai import OpenAI
import json
# api_key = os.environ["DEEPSEEK_API_KEY"]
# base_url = os.environ["DEEPSEEK_API_BASE"]
# model_type=os.environ["DEEPSEEK_MODEL"]
api_key = "sk-661***03f" # os.environ["DEEPSEEK_API_KEY"]
base_url ="https://api.deepseek.com/v1"# os.environ["DEEPSEEK_API_BASE"]
model_type= "deepseek-chat" #os.environ["DEEPSEEK_MODEL"]
# print(api_key)
print(base_url)
class MCPClient:
def __init__(self):
# Initialize session and client objects
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.anthropic = OpenAI(api_key=api_key, base_url=base_url)
# api_key=os.environ.get("ANTHROPIC_API_KEY")
# api_base=os.environ.get("ANTHROPIC_API_KEY")
# methods will go here
async def connect_to_server(self, server_script_path: str):
"""Connect to an MCP server
Args:
server_script_path: Path to the server script (.py or .js)
"""
is_python = server_script_path.endswith('.py')
is_js = server_script_path.endswith('.js')
if not (is_python or is_js):
raise ValueError("Server script must be a .py or .js file")
command = "python" if is_python else "node"
server_params = StdioServerParameters(
command=command,
args=[server_script_path],
env=None
)
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
await self.session.initialize()
# List available tools
response = await self.session.list_tools()
tools = response.tools
print("\nConnected to server with tools:", [tool.name for tool in tools])
async def connect_to_sse_server(self, server_url: str):
"""Connect to an MCP server running with SSE transport"""
# 创建 SSE 客户端连接上下文管理器
self._streams_context = sse_client(url=server_url)
# 异步初始化 SSE 连接,获取数据流对象
streams = await self._streams_context.__aenter__()
# 使用数据流创建 MCP 客户端会话上下文
self._session_context = ClientSession(*streams)
# 初始化客户端会话对象
self.session: ClientSession = await self._session_context.__aenter__()
# 执行 MCP 协议初始化握手
await self.session.initialize()
async def process_query(self, query: str) -> str:
"""Process a query using Claude and available tools"""
messages = [
{
"role": "user",
"content": query
}
]
response = await self.session.list_tools()
available_tools = []
for tool in response.tools:
tool_schema = getattr(
tool,
"inputSchema",
{"type": "object", "properties": {}, "required": []},
)
openai_tool = {
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool_schema,
},
}
available_tools.append(openai_tool)
# Initial Claude API call
model_response = self.anthropic.chat.completions.create(
model=model_type,
max_tokens=1000,
messages=messages,
tools=available_tools,
)
# Process response and handle tool calls
final_text = []
tool_results = []
messages.append(model_response.choices[0].message.model_dump())
print(messages[-1])
if model_response.choices[0].message.tool_calls:
tool_call = model_response.choices[0].message.tool_calls[0]
tool_args = json.loads(tool_call.function.arguments)
tool_name = tool_call.function.name
result = await self.session.call_tool(tool_name, tool_args)
tool_results.append({"call": tool_name, "result": result})
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
messages.append(
{
"role": "tool",
"content": f"{result}",
"tool_call_id": tool_call.id,
}
)
# Get next response from Claude
response = self.anthropic.chat.completions.create(
model=model_type,
max_tokens=1000,
messages=messages,
)
messages.append(response.choices[0].message.model_dump())
print(messages[-1])
return messages[-1]["content"]
async def chat_loop(self):
"""Run an interactive chat loop"""
print("\nMCP Client Started!")
print("Type your queries or 'quit' to exit.")
print("\n🤖 MCP 客户端已启动!输入 'quit' 退出")
while True:
try:
query = input("\nQuery: ").strip()
if query.lower() == 'quit':
break
response = await self.process_query(query)
print("\n" + response)
except Exception as e:
print(f"\nError: {str(e)}")
async def cleanup(self):
"""Clean up resources"""
await self.exit_stack.aclose()
async def main():
if len(sys.argv) < 2:
print("Usage: python client.py <path_to_server_script>")
sys.exit(1)
print("Connecting to server...")
print(sys.argv)
client = MCPClient()
try:
await client.connect_to_sse_server(sys.argv[1])
await client.chat_loop()
finally:
await client.cleanup()
if __name__ == "__main__":
import sys
asyncio.run(main())
三、股票数据查询示例


总结
本文详细介绍了如何使用DeepSeek、MCP协议和AKShare构建智能金融问答系统。通过MCP协议,我们成功将DeepSeek大模型的推理能力与AKShare的金融数据获取能力相结合,实现了真正的Function Calling功能。这种架构具有以下优势:
-
标准化:MCP提供了统一的接口标准,便于扩展其他数据源1
-
灵活性:可以轻松切换不同的大模型或数据源
-
安全性:敏感数据可以保留在本地环境中1
-
高效性:利用DeepSeek-V3的高性能推理能力3
未来可以进一步扩展的方向包括:
-
增加更多金融数据源
-
实现更复杂的投资分析功能
-
加入可视化图表生成能力
-
部署为Web服务供更多人使用
声明:本文仅做技术研究,投资有风险,如实需谨慎。