Skip to main content
Glama

MCP Stock Assistant

api.ts6.67 kB
import fetch from "node-fetch"; import type { Response } from "node-fetch"; import { COMMON_HEADERS, SEARCH_API, QUOTE_API, HISTORY_API, INDEX_API } from "./constants.js"; import type { SearchResponse, QuoteResponse, HistoryResponse, IndexResponse, StockData, HistoryDataPoint, IndexData, } from "./types.js"; import { getStockMarket } from "./utils.js"; async function makeRequest( url: string, params?: Record<string, string> ): Promise<Response> { const finalUrl = params ? `${url}?${new URLSearchParams(params)}` : url; const options = { headers: COMMON_HEADERS, }; try { const response = await fetch(finalUrl, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response; } catch (error) { console.error(`Error making request to ${url}:`, error); throw error; } } export async function searchStock(keyword: string): Promise<[string, string] | null> { try { const params = { input: keyword, type: "14", token: "D43BF722C8E33BDC906FB84D85E326E8", count: "5", }; const response = await makeRequest(SEARCH_API, params); const data = (await response.json()) as SearchResponse; if (data.QuotationCodeTable.Status === 0 && data.QuotationCodeTable.Data) { for (const item of data.QuotationCodeTable.Data) { if ( item.SecurityTypeName === "沪A" || item.SecurityTypeName === "深A" ) { return [item.Code, item.Name]; } } } return null; } catch (error) { console.error("Error searching stock:", error); return null; } } export async function fetchStockHistory( stockInput: string, period: string = "101", days: number = 30 ): Promise<HistoryDataPoint[] | null> { try { let stockCode = stockInput; if (!stockInput.match(/^\d{6}$/)) { const searchResult = await searchStock(stockInput); if (!searchResult) { console.error(`未找到股票:${stockInput}`); return null; } [stockCode] = searchResult; } const [market, fullCode] = getStockMarket(stockCode); const params = { secid: fullCode, klt: period, fqt: "1", lmt: days.toString(), end: "20500101", iscca: "1", fields1: "f1,f2,f3,f4,f5,f6", fields2: "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61", }; const response = await makeRequest(HISTORY_API, params); const data = (await response.json()) as HistoryResponse; if (data.rc !== 0 || !data.data || !data.data.klines) { throw new Error(data.msg || "获取历史数据失败"); } const historyData: HistoryDataPoint[] = data.data.klines.map((kline) => { const parts = kline.split(","); return { date: parts[0], open: parseFloat(parts[1]), close: parseFloat(parts[2]), high: parseFloat(parts[3]), low: parseFloat(parts[4]), volume: parseInt(parts[5]) / 100, amount: parseFloat(parts[6]) / 10000, changePercent: parseFloat(parts[8]), }; }); return historyData.reverse(); } catch (error) { console.error("Error fetching stock history:", error); return null; } } export async function fetchMarketIndex(): Promise<IndexData[] | null> { try { const params = { fltt: "2", invt: "2", fields: "f1,f2,f3,f4,f12,f13,f14", secids: "1.000001,0.399001", }; const response = await makeRequest(INDEX_API, params); const data = (await response.json()) as IndexResponse; if (data.rc !== 0 || !data.data || !data.data.diff) { throw new Error(data.msg || "获取指数数据失败"); } const indexData: IndexData[] = data.data.diff.map((item) => ({ code: item.f12, name: item.f14, price: item.f2, changePercent: item.f3, change: item.f4, volume: 0, amount: 0, time: new Date().toLocaleTimeString(), })); return indexData; } catch (error) { console.error("Error fetching market index:", error); return null; } } export async function fetchStockData(stockInput: string): Promise<StockData | null> { try { let stockCode = stockInput; let stockName = ""; if (!stockInput.match(/^\d{6}$/)) { const searchResult = await searchStock(stockInput); if (!searchResult) { console.error(`未找到股票:${stockInput}`); return null; } [stockCode, stockName] = searchResult; console.error(`找到股票:${stockName}(${stockCode})`); } const [market, fullCode] = getStockMarket(stockCode); const params = { secid: fullCode, fields: "f43,f57,f58,f169,f170,f46,f44,f51,f168,f47,f164,f163,f116,f60,f45,f52,f50,f48,f167,f117,f71,f161,f49,f530,f135,f136,f137,f138,f139,f141,f142,f144,f145,f147,f148,f140,f143,f146,f149,f55,f62,f162,f92,f173,f104,f105,f84,f85,f183,f184,f185,f186,f187,f188,f189,f190,f191,f192,f206,f207,f208,f209,f210,f211,f212,f213,f214,f215,f86,f107,f111,f86,f177,f78,f110", }; const response = await makeRequest(QUOTE_API, params); const data = (await response.json()) as QuoteResponse; if (data.rc !== 0 || !data.data) { throw new Error(data.msg || "获取数据失败"); } const quote = data.data; const buyOrders = []; const sellOrders = []; for (let i = 1; i <= 5; i++) { const buyPrice = quote[`f${18 + i * 2}`]; const buyVolume = quote[`f${17 + i * 2}`]; const sellPrice = quote[`f${10 + i * 2}`]; const sellVolume = quote[`f${9 + i * 2}`]; if (typeof buyPrice === "number" && typeof buyVolume === "number") { buyOrders.push({ price: buyPrice / 100, volume: buyVolume / 100, }); } if (typeof sellPrice === "number" && typeof sellVolume === "number") { sellOrders.push({ price: sellPrice / 100, volume: sellVolume / 100, }); } } return { code: stockCode, name: quote.f58, price: quote.f43 / 100, change: quote.f169 / 100, changePercent: quote.f170 / 100, volume: quote.f47 / 100, amount: quote.f48 / 10000, high: quote.f44 / 100, low: quote.f45 / 100, open: quote.f46 / 100, lastClose: quote.f60 / 100, turnoverRate: quote.f168 / 100, peRatio: quote.f162, amplitude: quote.f171 ? quote.f171 / 100 : 0, time: new Date().toLocaleTimeString(), buyOrders, sellOrders, }; } catch (error) { console.error("Error fetching stock data:", error); return null; } }

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/qqzhangyanhua/mcp-stock'

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