
最近折腾了一个叫stock-sdk-mcp的小项目本质就是把股票数据 SDK 的能力包装成 MCP Server让 Claude、Cursor 这类 AI 工具可以直接“开口”问行情。这段时间 MCP 生态确实火从 Figma MCP、Blender MCP 到各种奇奇怪怪的工具都在往这个协议上靠。我最初也只是好奇觉得 AI 能直接调工具是件挺酷的事但真正动手把一个 SDK 封装成 MCP 服务之后才发现里面有不少设计取舍和坑值得记录下来。这套东西说白了解决了一个很实际的问题以前想让 AI 帮忙查个股票实时价格、拉一段 K 线数据要么把数据喂进上下文里要么让 AI 给你写段 Python 代码然后你再去跑。这两种方式都很别扭前者浪费 token后者绕了一大圈。stock-sdk-mcp的思路是把数据能力用 MCP 协议暴露出来AI 需要数据时直接通过标准方式调用像一个后厨的订单窗口AI 点单服务端出菜清爽直接。如果你手上有一堆现成的数据接口或 SDK正想着怎么让 AI Agent 用起来又或者刚好想了解 MCP 协议怎么落地上线这篇实践整理应该能帮你少走几条弯路。里面不会有太多空洞的概念灌输更多是具体设计、代码实现、踩坑记录。1. 项目背景与核心痛点先聊聊为什么需要这样一个项目。很多人第一次接触 MCP 会问SDK 本来就是给开发者调用的AI 工具只要会写代码不就行了为什么还要多包一层协议这个问题很关键。AI Agent 虽然能写代码但不是所有场景都适合“写代码—执行—读输出”的模式。尤其涉及行情数据时你还需要鉴权、频率控制、错误重试、数据清洗这些逻辑每次都让 AI 现场写一遍既不安全也不稳定。更现实的问题在于像 Claude Desktop、Cursor 这类客户端默认是拿不到你本地环境里的 SDK 权限的它们运行在受控的沙箱环境里不能随便动你的系统和文件。MCP 在这里就扮演了一个桥的角色把你自己封装好的数据能力安全地暴露给 AI。1.1 为什么是 MCP而不是 Agent Skill 或 Function Call做这个项目之前我也认真对比过几种方案。现在网上关于 MCP 和 Agent Skill 有什么区别的讨论挺多我的理解是这样Agent Skill 更像是给 AI 的“操作手册”告诉它遇到什么场景该用什么工具、该怎么组合侧重行为编排MCP 则更像硬件上的 USB-C 接口它定义了一套标准协议把工具能力统一暴露出来任何支持 MCP 的客户端都可以即插即用。Function Call 的概念在 OpenAI 生态里很早就有了但它和具体厂商绑定换一个模型平台就要重新适配。MCP 的好处是协议标准化程度更高Claude、Cursor、甚至自己写的 Web 应用都能接。所以我在设计stock-sdk-mcp时选择直接拥抱 MCP 协议而不是为某一个模型单独做 Function Call 适配。1.2 stock-sdk-mcp 具体做什么这个项目从功能上讲就是把传统的股票数据调用方式转换成了 AI 可以直接理解的结构化服务。举个例子以前你要搞到一只股票的日 K 线大概要经历找到数据源文档、安装 SDK、写请求代码、处理返回的 JSON 字符串、再自己组装成表格。而现在你在 Cursor 里直接问“帮我看看平安银行最近 30 天的日 K 线走势”MCP Server 会负责把“平安银行”映射成股票代码 000001调用底层 SDK 拉取数据格式化好再返回给 AIAI 再基于这些数据帮你做分析。整个流程我整理了一张工具能力表工具名称功能说明核心参数get_stock_quote获取实时行情快照现价、涨跌幅、成交量等code、marketget_stock_kline获取历史 K 线数据支持日/周/月级别code、period、countget_stock_list获取指定市场的股票列表market、keywordsearch_stock根据名称或代码模糊搜索股票keywordget_market_index获取大盘指数行情codeget_stock_finance获取基本面财务数据摘要code工具数量不算多但覆盖了日常做投研分析最常用到的几个数据场景。而且每个工具都不需要 AI 去理解复杂的 HTTP 请求、签名逻辑、字段映射关系它只需要把参数填进去拿到结果后专注于分析本身。2. 整体设计与技术选型说实话把一个 SDK 封装成 MCP Server 并不复杂真正需要动脑子的是设计决策。比如传输方式选 stdio 还是 SSE数据源用免费的还是付费的底层代码用 Python 还是 TypeScript。这些选型直接决定了项目后期的使用体验和维护成本。2.1 传输方式stdio 还是 HTTP/SSEMCP 协议目前支持两种主流传输方式标准输入输出stdio和 HTTP 的 SSE 模式。这两种方式的适用场景差别还是挺大的。stdio 模式适合本地客户端比如 Claude Desktop、Cursor 桌面版。客户端启动时会拉起你的 MCP Server 进程两者通过标准输入输出通信好处是没有网络监听、零配置、安全性高。我在开发测试阶段百分之九十的时间都用它非常方便。HTTP/SSE 模式则适合把服务部署在远端或者同时给多个客户端提供能力。比如你在内网服务器上部署了一套数据服务想让办公室里的几个人都能通过 AI 工具访问这时候基于 HTTP 的 MCP Server 就是更合适的方案。Sse 模式把请求通过 HTTP 发出去响应通过 EventStream 推回来整体做下来也不算复杂但要额外处理跨域、鉴权、并发这些事。我这个项目的最终实现是同时支持两种模式。本地预览时用 stdio部署到内网服务器上时切到 SSE。MCP Python SDK 对这两种模式的支持都比较成熟切换成本不高关键是在设计阶段把服务逻辑和传输层解耦。2.2 数据源与 SDK 选型免费还是付费股票数据来源是最核心的决策。国内可选择的数据源无非几类一是第三方聚合平台如 Tushare、AkShare二是各券商或数据服务商提供的付费 API三是直接抓取财经网站公开接口。我在这个项目里采用了“分层数据源”的策略主数据源用公开的免费行情接口底层 SDK 根据可用性自动兜底。之所以不直接依赖某一个收费 SDK一方面是因为个人项目不想承担太高的数据成本另一方面是免费接口的稳定性虽然一般但做 AI 分析场景已经完全够用。如果哪一天免费接口挂了只需要在数据源适配层新增一个方法不需要改动 MCP 工具暴露给 AI 的部分。这里有个很重要的工程原则MCP 工具层对外暴露的返回格式必须是稳定的底层数据源可以随时切换。这个思路有点像依赖倒置把变的部分隔离在适配层里。我见过一些项目把某个数据源的字段结构直接透传给 AI结果数据源一改文档AI 工具就全瞎了。稳不稳就看这一层设计得好不好。2.3 Python FastMCP 的取舍技术栈我选了 Python FastMCP。原因很直接Python 在数据领域生态最好行情库、分析库都是现成的FastMCP 框架本身封装得很干净通过装饰器就能快速注册工具学习曲线也低。如果你之前没接触过 FastMCP可以理解成一个专门针对 MCP 协议做好的快速开发框架类似 Flask 之于 Web。它内部帮你处理了协议握手、请求分发、响应格式化这些事情开发者只需要关注自己提供的工具函数本身。当然如果你对 TypeScript 更熟官方也有 TypeScript SDK。选哪个没有标准答案但团队里如果有 Python 基础用它来搞数据类 MCP Server 会舒服很多。3. 核心实现从 SDK 到 MCP 工具层这一节是整篇的实操重点我会把从 SDK 到 MCP 工具层的核心代码结构和设计思路拆开讲。尽量写得详细一点因为很多坑是跑完一遍代码之后才会发现的。3.1 工具定义与 JSON Schema 设计MCP 工具层本质上就是一组带描述的 JSON Schema。AI 客户端通过协议拿到这些 Schema就会知道有哪些工具可用、每个工具需要哪些参数。所以“写工具名描述”这件事比很多人想象的重要得多。一个好的工具描述应该同时说清楚三件事这个工具是干什么的、输入参数是什么含义、输出会在什么范围内波动。不要小看后面的范围说明AI 模型对浮点数字精度非常敏感如果你返回的数据格式不稳定模型在解读时就会出现幻觉。我举个例子最初我给get_stock_quote的返回字段直接用price后来发现不同数据源给出的价格字段精度不一样有的带两位小数有的带四位。AI 模型在分析时如果没被告知价格精度很可能做出“股价在 10 到 10.0000 之间波动”这种离谱判断。所以在设计工具层时我给每个返回字段都加了描述比如“price 表示最新成交价单位为元保留两位小数”。下面是 FastMCP 里一个实际工具的简化代码from fastmcp import FastMCP mcp FastMCP(stock-sdk-mcp) mcp.tool() def get_stock_quote(code: str, market: str sh) - dict: 获取股票实时行情快照。 Args: code: 股票代码如 000001 market: 股票市场sh 表示上交所sz 表示深交所 Returns: 包含最新价、涨跌幅、成交量、成交额等字段的字典 from data_source import fetch_quote data fetch_quote(market, code) return { code: code, market: market, price: float(data.get(price, 0)), change_pct: float(data.get(change_pct, 0)), volume: int(data.get(volume, 0)), amount: float(data.get(amount, 0)), timestamp: data.get(timestamp, ) }3.2 数据兼容层统一返回格式数据源层的设计决定了这个项目的上限。我一开始觉得行情接口返回什么我就透传什么就行后来发现免费接口的返回结构不统一有嵌套字典的、有纯字符串拼逗号的、有直接给中文 key 的。如果这些脏数据直接进入 AI 上下文不仅浪费 token还会严重影响后续分析的质量。所以我做了一件事定义一套标准化的数据模型所有数据源返回的数据都先映射到这套模型上再由工具层输出给 AI。这个标准化过程相当于给底层 SDK 加了“翻译官”。举个例子有的数据源把涨跌幅字段叫change_percent有的叫涨跌幅在我的适配层里它们都会被翻译为change_pct。这种设计带来两个好处第一AI 看到的永远是干净、结构一致的数据第二换数据源时只需要改适配层对上层完全没有影响。做 MCP Server 的人很容易忽略这个标准化步骤但这恰恰是决定项目长期维护顺不顺畅的分水岭。3.3 异步调度、超时控制与缓存机制MCP Server 跑起来之后你会发现它不只是一个接口转发器更像一个带调度逻辑的中间层。AI 客户端可能会同时发多个请求过来比如它要对比五只股票就会并发调用五次get_stock_quote。如果底层 SDK 是同步阻塞式的整个服务就会卡在慢请求上AI 那边表现为长时间没响应。我在实现里做了两层优化。第一层是异步封装底层 SDK 的同步方法通过线程池转成异步协程避免一个慢请求阻塞整个事件循环。第二层是超时控制免费行情接口偶尔会有 5 到 10 秒的延迟如果超过 3 秒无响应就直接给 AI 返回“数据源超时请稍后重试”而不是让 AI 干等。还有一个容易被忽略的点是缓存。行情数据里的分钟级快照其实没必要每次都去数据源拉一遍我在服务里加了一个简单的 TTL 缓存有效期 5 秒。这个时长的选择不是拍脑袋定的而是权衡了数据时效性和数据源频率限制后得出的结果。太短了缓存没有意义太长了你报的价格就失真了。4. 实操全流程从零搭建并接上 Claude Desktop前面把设计和原理都讲完了这部分直接从零开始动手。我假设你已经有一台能跑 Python 的电脑系统是 Windows 或 macOS 都行接下来就按步骤搭。4.1 环境准备与项目初始化首先创建项目目录并初始化虚拟环境我习惯用uv管理 Python 项目比 pip 加 venv 方便不少mkdir stock-sdk-mcp cd stock-sdk-mcp uv init source .venv/bin/activate # Windows 下是 .venv\Scripts\activate uv add fastmcp requests pandas这里解释一下依赖选择。fastmcp是 MCP Server 的核心框架用来快速注册工具和实现协议层。requests是我自己的数据源适配层要用的 HTTP 客户端。pandas一开始其实没打算加后来在 K 线数据处理时发现做数据对齐确实省事就留下了。4.2 服务端核心代码数据源适配层我的项目结构里单独拆了一个data_source.py模块专门负责和底层数据接口沟通。这个模块在整篇文章里是灵魂MCP 工具层不要直接碰 HTTP 请求所有网络请求都收敛到这里。import requests from datetime import datetime # 本地行情接口的基础 URL正式使用请替换为真实数据源 BAST_URL https://your-data-source.example.com def _request(url: str, params: dict) - dict: resp requests.get(url, paramsparams, timeout3) resp.raise_for_status() return resp.json() def fetch_quote(market: str, code: str) - dict: 获取单只股票的实时行情快照。 url f{BAST_URL}/{market}/{code}/quote raw _request(url, {}) # 这里做字段标准化统一输出固定键名 return { price: raw.get(now, 0), change_pct: raw.get(change_pct, 0), volume: raw.get(volume, 0), amount: raw.get(turnover, 0), timestamp: datetime.now().strftime(%Y-%m-%d %H:%M:%S) } def fetch_kline(market: str, code: str, period: str, count: int) - list[dict]: 获取历史 K 线返回按时间正序排列的记录列表。 url f{BAST_URL}/{market}/{code}/kline params {period: period, count: count} raw _request(url, params) records [] for item in raw.get(records, []): records.append({ date: item.get(date, ), open: float(item.get(open, 0)), high: float(item.get(high, 0)), low: float(item.get(low, 0)), close: float(item.get(close, 0)), volume: int(item.get(volume, 0)) }) return records每个方法做且只做一件事请求上游接口拿回数据统一字段命名。这一步千万不要图省事把raw直接返回否则你在调试 AI 输出时会被各种奇奇怪怪的字段名搞到崩溃。4.3 服务端核心代码MCP 工具注册接下来是 MCP 工具的注册入口。我在server.py里定义了所有对外暴露的工具函数并加上详细的文档字符串。你没看错文档字符串在 MCP 里就是“规格说明书”AI 能不能理解你的工具全靠它。from fastmcp import FastMCP from data_source import fetch_quote, fetch_kline mcp FastMCP(stock-sdk-mcp) mcp.tool() def get_stock_quote(code: str, market: str sh) - dict: 获取指定股票的实时行情快照包括最新价格、涨跌幅度、成交量和成交金额。 返回字段说明 - price: 最新成交价单位元保留两位小数 - change_pct: 涨跌幅单位百分比 - volume: 成交量单位股 - amount: 成交金额单位元 - timestamp: 行情时间戳 return fetch_quote(market, code) mcp.tool() def get_stock_kline(code: str, market: str sh, period: str day, count: int 30) - list[dict]: 获取指定股票的历史 K 线数据。 Args: code: 股票代码 market: 股票市场sh 或 sz period: K线周期day 日线、week 周线、month 月线 count: 需要返回的K线根数取值范围 1 到 500 Returns: 按时间正序排列的记录列表每一条包含日期、开高低收和成交量。 if count 1 or count 500: raise ValueError(count 参数必须在 1 到 500 之间) return fetch_kline(market, code, period, count) if __name__ __main__: mcp.run(transportstdio)可能有人会问为什么我在参数校验这里卡得这么死。原因很简单AI 模型对边界条件的把握很不稳定如果它想请求 10000 条 K 线而你的数据源只支持最大 1000 条返回的结果就会让 AI 的后续分析建立在错误前提下。显式校验并报错反而比悄悄截断更容易让 AI 理解发生了什么。4.4 对接客户端Claude Desktop 与 Cursor写完了 MCP Server就要把它接到客户端里用了。Claude Desktop 的配置方式是在配置文件里增加一个mcpServers条目。找到配置文件的路径之后macOS 在~/Library/Application Support/Claude/Windows 在%APPDATA%\Claude\增加下面这段{ mcpServers: { stock-sdk-mcp: { command: uv, args: [run, --directory, /absolute/path/to/stock-sdk-mcp, server.py] } } }注意这里我用的是uv run而不是直接指定 Python 路径。这样做的目的是让 Client 每次启动 MCP Server 时都能自动加载虚拟环境里的依赖避免出现“找不到 fastmcp 模块”之类的环境问题。如果你更习惯手动管理虚拟环境直接用/path/to/.venv/bin/python server.py也是可以的但务必把command和args都写绝对路径。连接好之后你在 Claude Desktop 里直接提问“帮我查一下贵州茅台的当前股价”正常情况下它会自动调用get_stock_quote工具并返回一个结构化的行情信息。如果在某个客户端里没有看到工具被调用优先检查客户端的日志文件基本都会有明确的报错提示。5. 踩坑记录与常见问题排查这部分说点实在的。项目跑通不难但想让它在不同电脑、不同客户端环境下稳定复用好几个坑是躲不掉的。我整理了一个速查表另外把几条最典型的排查过程单独展开了。5.1 常见问题速查表问题现象可能原因解决办法客户端启动后 MCP Server 没反应command 路径配置错误或虚拟环境未激活终端手动执行一遍启动命令看有没有报错调用工具时报超时数据源响应较慢检查免费接口的时效性适当调大超时参数返回中文乱码Windows 控制台编码问题在代码里显式设置 UTF-8 输出或调整终端代码页模型不理解工具参数文档字符串描述不清晰重写函数 docstring附上参数取值范围和返回字段说明MCP Server 进程频繁退出依赖环境不一致确保客户端启动的是同一个虚拟环境不要混用系统 Python报错 SDK not found漏装了底层 SDK确认你是否真的需要底层 SDK直接用 HTTP 适配层就够了5.2 客户端连不上服务端的排查思路这是一个高频问题值得单独讲一下排查思路。很多人配置完成之后发现客户端里看不到任何工具或者工具调用后一直没有输出。我的做法是三步排查法。第一步在终端手动启动 MCP Server确认服务本身能跑。这是最基础的一步如果直接在终端跑python server.py都报错那问题在代码而不是客户端配置。第二步检查客户端的日志文件。Claude Desktop 的日志通常记录得非常详细如果 MCP Server 连接失败日志里会有明确的错误码和路径信息。第三步确认客户端启动 MCP Server 时的命令行工作目录。有些客户端对相对路径支持不好建议把项目路径和入口脚本全部改成绝对路径。这套排查思路在绝大多数情况下都能解决问题。如果你不够确定哪一步出了问题把日志文件里最后 20 行贴给 AI 工具本身它往往能帮你快速定位到问题。我自己调试时就这么干过比自己枯想要快得多。5.3 我个人的几条经验心得整个项目做到最后有几个体会很值得分享。第一工具的数量不是越多越好。MCP 的工具列表每多一个模型在选择时的干扰就多一分。我一开始暴露了十来个工具里面有好多使用频率很低的边缘功能结果 AI 反而容易选错。后来砍掉一半只保留高频核心方法整体效果反而稳定了。这点也和社区的普遍观点一致MCP Server 设计更像产品设计克制比堆功能重要。第二参数的默认值要给得足够“安全”。如果某个工具拿默认参数时返回的数据明显不靠谱模型就不会信任这个工具。比如 K 线数据的默认周期是日线默认数量是 30 根这两个值在大多数行情分析场景下都是合理的模型可以直接调用并得到有意义的回答。养成“默认值即可用”的习惯对 AI 类产品非常重要。第三不要忽视服务端的日志输出。MCP 工具层的日志价值巨大因为你能看到每一次模型发起调用的完整参数和响应状态。这不仅有助于排查问题还能让你更好地理解模型的行为模式。如果长期开着服务但从不看日志等于浪费了一个非常有价值的数据源。如果你也正在折腾自己的 MCP Server建议从一套简单的工具开始跑通链路之后再不断迭代。等把这套思维捋顺了再回头看任何 SDK 都能很快包装成标准化的 AI 能力。