1. 为什么要在本地跑一个股票行情 MCP Server
MCP Server 这个词最近出现频率很高,但很多人第一次接触会有点懵:它到底解决什么问题?简单说,MCP(Model Context Protocol)是一套让大语言模型调用外部工具的协议,你可以把它理解成「给 AI 装插件的标准接口」。模型本身不知道今天的股价,但通过 MCP Server 暴露一个get-quote工具,模型就能在对话里直接查行情。
这篇要做的,是用 Node.js 从零写一个能获取实时股票价格的 MCP Server,然后把它接到 TaoToken 的统一 Key/API 通道上,让本地 AI 工具(比如 Claude Code、Cursor 这类支持 MCP 的客户端)真正调用一次行情数据。适合谁看:会一点 JavaScript、想让 AI 助手能查股票、但不想折腾一堆鉴权和网络配置的人。
我试过把行情查询塞进普通脚本里,问题是每次都要手动跑;换成 MCP Server 之后,模型自己决定什么时候调、调哪只股票,体验完全不一样。下面从环境准备开始,一步步跑通。
2. TaoToken 前置准备:统一 Key 与接入地址
在写代码之前,先把「通道」准备好。TaoToken 的作用是提供一个统一的 API 入口和 Key,你不需要为每个模型或工具单独配一套鉴权。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个不加 UTM)。
你需要做两件事:
第一,拿到 API Key。登录后进控制台,在 API Keys 页面创建一个新 Key,复制保存。这个 Key 后面会写进 MCP 客户端的配置里,用来让模型侧请求走 TaoToken 通道。
第二,确认你要用的模型对话入口。如果你只是想验证 MCP Server 能不能被调用,用模型对话页面就够了;如果你打算长期在编码工具里用,建议看 Coding Plan,它更适合 Agent 场景。
注意:Key 只创建一次就够,不要把它硬编码进要提交到 Git 的源码里。用环境变量或客户端配置文件承载。
这里有个容易踩的坑:很多人以为 MCP Server 自己要去请求 TaoToken,其实不是。MCP Server 只负责「查行情」这一件事,它请求的是行情数据源;TaoToken 的 Key 是配在 MCP 客户端(也就是调用方)那一侧的,用来让模型能正常对话并触发工具调用。两者职责分开,别混在一起。
3. 可复制配置:项目骨架与 MCP Server 代码
先建项目。Node.js 版本建议 18 以上,因为要用到内置的fetch。
mkdir stock-mcp-server && cd stock-mcp-server npm init -y npm install @modelcontextprotocol/sdk zod然后在package.json里加上"type": "module",因为下面用的是 ESM 语法。接着创建server.js,核心逻辑分三块:请求行情、解析数据、注册工具。
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; const SINA_API_BASE = 'https://hq.sinajs.cn/list'; const server = new McpServer({ name: 'stock', version: '1.0.0', capabilities: { resources: {}, tools: {} }, }); async function makeStockRequest(symbol) { const url = `${SINA_API_BASE}=${symbol}`; const headers = { Referer: 'https://finance.sina.com.cn' }; try { const response = await fetch(url, { headers }); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const buffer = await response.arrayBuffer(); const decoder = new TextDecoder('gb2312'); const text = decoder.decode(buffer); return parseSinaStockData(text, symbol); } catch (error) { console.error('Error making stock request:', error); return null; } } function parseSinaStockData(text, symbol) { const matches = text.match(/"(.*)"/); if (!matches || !matches[1]) return null; const values = matches[1].split(','); if (values.length < 32) return null; return { symbol, name: values[0], open: values[1], close: values[2], price: values[3], high: values[4], low: values[5], volume: values[8], amount: values[9], date: values[30], time: values[31], }; } function formatQuote(quote) { return [ `Stock Code: ${quote.symbol}`, `Stock Name: ${quote.name}`, `Current Price: ¥${quote.price}`, `Change Rate: ${(((Number(quote.price) - Number(quote.close)) / Number(quote.close)) * 100).toFixed(2)}%`, `Open Price: ¥${quote.open}`, `High Price: ¥${quote.high}`, `Low Price: ¥${quote.low}`, `Volume: ${(Number(quote.volume) / 100).toFixed(0)} lots`, `Turnover: ¥${(Number(quote.amount) / 10000).toFixed(2)} million`, `Update Time: ${quote.date} ${quote.time}`, ].join('\n'); } server.tool( 'get-quote', 'Get real-time stock quote', { symbol: z.string().describe('Stock symbol (e.g.: sh600000, sz000001)') }, async ({ symbol }) => { const quoteData = await makeStockRequest(symbol); if (!quoteData) { return { content: [{ type: 'text', text: `Failed to retrieve stock data for ${symbol}` }] }; } return { content: [{ type: 'text', text: formatQuote(quoteData) }] }; } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('Stock MCP Server running on stdio'); } main().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); });几个关键点解释一下。StdioServerTransport表示这个 Server 通过标准输入输出和客户端通信,这是本地 MCP 最常见的模式。server.tool注册了一个名为get-quote的工具,参数用 zod 定义,模型看到描述后就知道该传什么。行情源返回的是 GB2312 编码,所以要用TextDecoder('gb2312')解码,直接当 UTF-8 读会乱码——这是实测下来最容易忽略的一步。
4. 接入配置:config.toml 与 settings.json 骨架
代码写完了,接下来是让客户端认识它。不同客户端配置格式不一样,这里给两个常见骨架。
Claude Code 用的是config.toml,在项目或用户目录下:
[mcp_servers.stock] command = "node" args = ["/absolute/path/to/stock-mcp-server/server.js"]Cursor 这类用settings.json:
{ "mcpServers": { "stock": { "command": "node", "args": ["/absolute/path/to/stock-mcp-server/server.js"] } } }路径一定要写绝对路径,相对路径在客户端启动时工作目录不确定,会找不到文件。如果你希望模型侧走 TaoToken 通道,把 API Key 配在客户端的模型设置里,而不是这个 MCP 配置块里。两者是并列关系:一个管「模型怎么连」,一个管「工具怎么跑」。
启动命令很简单:
node /absolute/path/to/stock-mcp-server/server.js正常的话终端不会有输出(因为 stdio 被占用了),只有出错才会打印到 stderr。看到Stock MCP Server running on stdio说明启动成功。
5. 验证请求:跑通一次真实行情查询
配置好之后重启客户端,在对话里直接问:「帮我查一下 sh600000 的实时价格」。模型应该会触发get-quote工具,返回类似这样的结果:
Stock Code: sh600000 Stock Name: 浦发银行 Current Price: ¥10.25 Change Rate: 0.59% Open Price: ¥10.20 High Price: ¥10.31 Low Price: ¥10.18 Volume: 123456 lots Turnover: ¥12345.67 million Update Time: 2025-01-15 14:30:00如果模型没有调用工具,先确认客户端是否识别到了 MCP Server(一般在设置里能看到已连接的工具列表)。如果工具被调用了但返回Failed to retrieve stock data,多半是 symbol 格式不对——A 股要带sh或sz前缀,比如sh600000、sz000001。
想单独测试 Server 本身,可以用 MCP Inspector:
npx @modelcontextprotocol/inspector node /absolute/path/to/stock-mcp-server/server.js它会打开一个网页界面,你可以手动调用get-quote并传参,看到原始返回。这一步能把「Server 问题」和「客户端配置问题」分开,排障效率高很多。
6. 本篇常见错排查
报错Cannot find module '@modelcontextprotocol/sdk':没装依赖,或者客户端启动时工作目录不对。回到项目目录跑npm install,配置里用绝对路径。
行情返回乱码:忘了用TextDecoder('gb2312')。行情源是 GB2312 编码,用默认 UTF-8 解码中文名会变成问号。
HTTP error! status: 403:请求头缺Referer。行情接口对来源有校验,加上Referer: 'https://finance.sina.com.cn'就好。
模型不调用工具:检查客户端是否加载了 MCP 配置,以及工具描述是否清晰。描述写「Get real-time stock quote」比写「stock」更容易被模型正确触发。
Key 相关报错:如果你在客户端里配了 TaoToken 的 Key 但对话报鉴权失败,去控制台确认 Key 是否有效、是否复制完整。接入文档里有各客户端的详细配置说明,对照检查一遍通常能定位。
排障时优先看 API Keys 和接入文档这两处,大部分问题都在配置层。验证模型是否正常响应可以用模型对话页面单独测;如果你是要长期在编码工具里跑 Agent,Coding Plan 的额度模型更适合持续调用。
把上面这套跑通之后,你其实已经掌握了 MCP Server 的通用套路:注册工具、处理请求、返回结构化文本。换成天气、汇率、数据库查询,结构几乎一样,改的是makeStockRequest里的数据源和解析逻辑。真正花时间的往往不是代码,而是编码、请求头、路径这些细节——踩过一次就记住了。