# Directory Structure ``` ├── fetch.py ├── mcp.json ├── package-lock.json ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Stock Analysis Server by Akshay Bavkar 2 | 3 | This is an MCP server that provides access to real-time and historical Indian stock data using the Yahoo Finance API. It allows stock data retrieval to be used as context by local LLMs via Claude Desktop, Cursor, and other MCP-compatible agents. 4 | 5 | ## Available Features 6 | 7 | - **getStockQuote**: Get the current quote for an Indian stock. 8 | - **getHistoricalData**: Get historical data for an Indian stock with custom intervals and periods. 9 | --- 10 | 11 | ## Setup 12 | 13 | ```bash 14 | npm install mcp-stock-analysis 15 | ``` 16 | 17 | ## Usage in Host 18 | Configure your MCP client (e.g., Claude Desktop) to connect to the server: 19 | 20 | ```JSON 21 | { 22 | "mcpServers": { 23 | "mcp-stock-analysis": { 24 | "command": "npx", 25 | "args": ["-y", "mcp-stock-analysis"], 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | ## Tools 32 | ### `getStockQuote` 33 | Get the current quote for a stock. 34 | 35 | Input: 36 | 37 | `symbol`: The stock symbol (e.g., RELIANCE.NS) 38 | 39 | Output: 40 | ```JSON 41 | { 42 | "symbol": "RELIANCE.NS", 43 | "price": 2748.15, 44 | "name": "Reliance Industries Ltd" 45 | } 46 | ``` 47 | 48 | ### `getHistoricalData` 49 | Get historical data for a stock. 50 | 51 | Input: 52 | 53 | - `symbol`: the stock symbol (e.g., RELIANCE.NS) 54 | - `interval`: the time interval for the data (`daily`, `weekly`, or `monthly`) (optional, default: `daily`) 55 | 56 | Output: 57 | ```JSON 58 | { 59 | "date": "2025-03-21T00:00:00+05:30", 60 | "open": 2735, 61 | "high": 2750, 62 | "low": 2725, 63 | "close": 2748.15, 64 | "volume": 21780769 65 | } 66 | ``` 67 | 68 | JSON object containing the historical data. The structure of the output depends on the interval parameter. 69 | 70 | ## Contributing 71 | Contributions are welcome! Please open an issue or pull request. 72 | 73 | 74 | ### License 75 | MIT 76 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "resolveJsonModule": true, 11 | "outDir": "./dist", 12 | "rootDir": "./src" 13 | }, 14 | "include": ["src/**/*.ts"], 15 | "exclude": ["node_modules"] 16 | } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "mcp-stock-analysis", 3 | "version": "1.0.0", 4 | "description": "MCP server for Indian stock market using yfinance", 5 | "type": "module", 6 | "main": "src/index.ts", 7 | "bin": { 8 | "mcp-stock-analysis": "dist/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc && chmod +x dist/index.js", 12 | "prepare": "npm run build", 13 | "start": "node dist/index.js" 14 | }, 15 | "files": [ 16 | "dist", 17 | "fetch.py" 18 | ], 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "^0.0.8" 21 | }, 22 | "devDependencies": { 23 | "typescript": "^5.3.3" 24 | } 25 | } ``` -------------------------------------------------------------------------------- /fetch.py: -------------------------------------------------------------------------------- ```python 1 | import sys 2 | import yfinance as yf 3 | import json 4 | 5 | mode = sys.argv[1] 6 | symbol = sys.argv[2] 7 | 8 | if mode == "quote": 9 | stock = yf.Ticker(symbol) 10 | info = stock.info 11 | result = { 12 | "symbol": symbol, 13 | "price": float(info.get("currentPrice") or info.get("regularMarketPrice")), 14 | "name": info.get("shortName") 15 | } 16 | print(json.dumps(result)) 17 | 18 | elif mode == "history": 19 | period = sys.argv[3] 20 | interval = sys.argv[4] 21 | stock = yf.Ticker(symbol) 22 | hist = stock.history(period=period, interval=interval).reset_index() 23 | result = [] 24 | for _, row in hist.iterrows(): 25 | result.append({ 26 | "date": row["Date"].isoformat(), 27 | "open": float(row["Open"]), 28 | "high": float(row["High"]), 29 | "low": float(row["Low"]), 30 | "close": float(row["Close"]), 31 | "volume": int(row["Volume"]) 32 | }) 33 | print(json.dumps(result)) ``` -------------------------------------------------------------------------------- /mcp.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "mcp-stock-analysis", 3 | "version": "0.1.0", 4 | "description": "A stock analysis tool that provides real-time and historical stock data", 5 | "author": "Your Name", 6 | "tools": [ 7 | { 8 | "id": "getStockQuote", 9 | "description": "Get the current price of an Indian stock", 10 | "parameters": { 11 | "symbol": { 12 | "type": "string", 13 | "description": "The stock symbol (e.g., RELIANCE.NS for Reliance Industries)" 14 | } 15 | } 16 | }, 17 | { 18 | "id": "getHistoricalData", 19 | "description": "Fetch historical stock prices", 20 | "parameters": { 21 | "symbol": { 22 | "type": "string", 23 | "description": "The stock symbol (e.g., RELIANCE.NS for Reliance Industries)" 24 | }, 25 | "period": { 26 | "type": "string", 27 | "description": "Time period (e.g., 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)", 28 | "default": "1mo" 29 | }, 30 | "interval": { 31 | "type": "string", 32 | "description": "Data interval (e.g., 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo)", 33 | "default": "1d" 34 | } 35 | } 36 | } 37 | ], 38 | "command": "npx tsx index.ts", 39 | "cwd": "." 40 | } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | // src/index.ts — MCP Server for Stock Analysis using yfinance (built for dist/ publishing) 2 | 3 | import { spawn } from "child_process"; 4 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 5 | import { 6 | ListToolsRequestSchema, 7 | CallToolRequestSchema, 8 | CallToolRequest, 9 | CallToolResult, 10 | Tool 11 | } from "@modelcontextprotocol/sdk/types.js"; 12 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 13 | 14 | function runPythonScript(args: string[]): Promise<any> { 15 | return new Promise((resolve, reject) => { 16 | const proc = spawn("python3", ["fetch.py", ...args]); 17 | let output = ""; 18 | let error = ""; 19 | 20 | proc.stdout.on("data", (data) => (output += data)); 21 | proc.stderr.on("data", (data) => (error += data)); 22 | 23 | proc.on("close", (code) => { 24 | if (code === 0) { 25 | try { 26 | resolve(JSON.parse(output)); 27 | } catch (err) { 28 | reject(`JSON parse error: ${err}`); 29 | } 30 | } else { 31 | reject(`Python error: ${error}`); 32 | } 33 | }); 34 | }); 35 | } 36 | 37 | const getStockQuoteTool: Tool = { 38 | name: "getStockQuote", 39 | description: "Get the current price of an Indian stock", 40 | inputSchema: { 41 | type: "object", 42 | properties: { 43 | symbol: { type: "string" } 44 | }, 45 | required: ["symbol"] 46 | }, 47 | outputSchema: { 48 | type: "object", 49 | properties: { 50 | symbol: { type: "string" }, 51 | price: { type: "number" }, 52 | name: { type: "string" } 53 | }, 54 | required: ["symbol", "price", "name"] 55 | } 56 | }; 57 | 58 | const getHistoricalDataTool: Tool = { 59 | name: "getHistoricalData", 60 | description: "Fetch historical stock prices", 61 | inputSchema: { 62 | type: "object", 63 | properties: { 64 | symbol: { type: "string" }, 65 | period: { type: "string", default: "1mo" }, 66 | interval: { type: "string", default: "1d" } 67 | }, 68 | required: ["symbol"] 69 | }, 70 | outputSchema: { 71 | type: "array", 72 | items: { 73 | type: "object", 74 | properties: { 75 | date: { type: "string" }, 76 | open: { type: "number" }, 77 | high: { type: "number" }, 78 | low: { type: "number" }, 79 | close: { type: "number" }, 80 | volume: { type: "number" } 81 | }, 82 | required: ["date", "open", "high", "low", "close", "volume"] 83 | } 84 | } 85 | }; 86 | 87 | const server = new Server( 88 | { 89 | name: "mcp-stock-analysis", 90 | version: "0.1.0", 91 | description: "A stock analysis tool that provides real-time and historical stock data", 92 | author: "Gipti Labs", 93 | capabilities: { 94 | tools: { 95 | getStockQuote: { 96 | description: "Get the current price of an Indian stock", 97 | parameters: { 98 | symbol: { 99 | type: "string", 100 | description: "The stock symbol (e.g., RELIANCE.NS for Reliance Industries)" 101 | } 102 | } 103 | }, 104 | getHistoricalData: { 105 | description: "Fetch historical stock prices", 106 | parameters: { 107 | symbol: { 108 | type: "string", 109 | description: "The stock symbol (e.g., RELIANCE.NS for Reliance Industries)" 110 | }, 111 | period: { 112 | type: "string", 113 | description: "Time period (e.g., 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)", 114 | default: "1mo" 115 | }, 116 | interval: { 117 | type: "string", 118 | description: "Data interval (e.g., 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo)", 119 | default: "1d" 120 | } 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | { 127 | capabilities: { 128 | tools: {}, 129 | }, 130 | } 131 | ); 132 | 133 | server.setRequestHandler(ListToolsRequestSchema, async () => ({ 134 | tools: [getStockQuoteTool, getHistoricalDataTool], 135 | })); 136 | 137 | server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest): Promise<CallToolResult> => { 138 | const { name, arguments: args } = request.params; 139 | 140 | if (name === "getStockQuote") { 141 | try { 142 | const input = args as { symbol: string }; 143 | const result = await runPythonScript(["quote", input.symbol]); 144 | return { 145 | content: [{ type: "text", text: JSON.stringify(result) }] 146 | }; 147 | } catch (error) { 148 | return { 149 | content: [{ type: "text", text: `Error: ${error}` }], 150 | isError: true 151 | }; 152 | } 153 | } else if (name === "getHistoricalData") { 154 | try { 155 | const input = args as { symbol: string; period?: string; interval?: string }; 156 | const period = input.period || "1mo"; 157 | const interval = input.interval || "1d"; 158 | const result = await runPythonScript(["history", input.symbol, period, interval]); 159 | return { 160 | content: [{ type: "text", text: JSON.stringify(result) }] 161 | }; 162 | } catch (error) { 163 | return { 164 | content: [{ type: "text", text: `Error: ${error}` }], 165 | isError: true 166 | }; 167 | } 168 | } else { 169 | return { 170 | content: [{ type: "text", text: "Unknown tool" }], 171 | isError: true 172 | }; 173 | } 174 | }); 175 | 176 | const transport = new StdioServerTransport(); 177 | await server.connect(transport); 178 | console.error("✅ MCP Server started and ready for requests..."); 179 | ```