MCP (Model Context Protocol) is Anthropic's open standard for connecting AI models to external tools and data sources. In 2026, it's become the plumbing layer that lets Claude query your databases, call your APIs, and read your files — all during a conversation, without you copy-pasting anything.
The existing MCP tutorial covers the TypeScript SDK and a generic file operations server. This one is different: Python, and we're connecting to real Indian financial data — BSE stock prices. The same pattern applies to any Indian API: Naukri jobs, GST verification, DPIIT startup registry, RBI data.
By the end you'll have a working MCP server that lets Claude answer "What's the current price of Reliance Industries?" by actually querying live data.
What MCP is (and the three primitives you need to know)
MCP works through three primitives:
- Tools — functions the model can call, like
get_stock_price(symbol). Claude decides when to call them based on your descriptions. - Resources — read-only data Claude can pull in, like a portfolio CSV or a config file. Good for structured reference data.
- Prompts — reusable prompt templates your server exposes. Less commonly used, but useful for consistent formatting.
The flow during a conversation:
- Claude reads your tool descriptions and decides a tool call is needed
- It sends the tool call to your MCP server (as a subprocess)
- Your server runs the code, returns the result
- Claude incorporates the result and continues
Think of it as Claude's way of calling functions in your codebase. The difference from regular function calling is that MCP is a standardised protocol — any MCP-compatible client (Claude Code, Claude Desktop, Cursor) can connect to your server without you writing custom integration code per client. See the what is MCP protocol post for the deeper conceptual overview.
Prerequisites
python --version # needs 3.10+
pip install mcp httpx
That's all. The mcp package from Anthropic includes FastMCP, a high-level API that handles protocol boilerplate. httpx is for async HTTP calls to external APIs.
You don't need Claude Desktop or Claude Code installed to build the server — but you'll want at least one of them to test it.
The server we're building: bse-data-server
Three tools:
get_stock_price(symbol)— current price and change for a BSE-listed stockget_company_info(symbol)— company name, sector, market capsearch_stocks(query)— search BSE listings by company name or symbol
Data source: NSE India's public JSON API (no auth required, no API key). We'll use NSEpy-compatible endpoints that return structured data.
The complete server is under 100 lines.
Writing the server
The minimal MCP server skeleton
Start with this — it's a working MCP server that does nothing useful yet:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("bse-data-server")
@mcp.tool()
async def hello(name: str) -> str:
"""Say hello to someone. Use this to test the server."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
Save it as bse_server.py and run python bse_server.py — you'll see no output, which is correct. MCP servers communicate over stdio, so they only produce output when a client connects.
The @mcp.tool() decorator is how you register tools. The docstring becomes the tool description Claude uses to decide when to call it — write it for Claude, not for humans.
Implementing the full server
import httpx
import json
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("bse-data-server")
# NSE India provides free public data endpoints
NSE_BASE = "https://www.nseindia.com/api"
HEADERS = {
"User-Agent": "Mozilla/5.0 (compatible; MCPBot/1.0)",
"Accept": "application/json",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://www.nseindia.com/",
}
async def fetch_nse(path: str) -> dict:
"""Make an authenticated-looking request to NSE API."""
async with httpx.AsyncClient(timeout=10.0) as client:
# NSE requires a cookie — get homepage first
await client.get("https://www.nseindia.com", headers=HEADERS)
response = await client.get(f"{NSE_BASE}{path}", headers=HEADERS)
response.raise_for_status()
return response.json()
@mcp.tool()
async def get_stock_price(symbol: str) -> str:
"""Get the current price, day change, and 52-week range for an NSE/BSE listed stock.
Use this when asked about current stock price, today's performance, or trading data.
Symbol examples: RELIANCE, TCS, INFY, HDFCBANK, WIPRO, ITC."""
try:
symbol = symbol.upper().strip()
data = await fetch_nse(f"/quote-equity?symbol={symbol}")
price_data = data.get("priceInfo", {})
meta = data.get("metadata", {})
last_price = price_data.get("lastPrice", "N/A")
change = price_data.get("change", 0)
pct_change = price_data.get("pChange", 0)
week_high = price_data.get("weekHighLow", {}).get("max", "N/A")
week_low = price_data.get("weekHighLow", {}).get("min", "N/A")
company_name = meta.get("companyName", symbol)
direction = "▲" if change >= 0 else "▼"
return (
f"{company_name} ({symbol})\n"
f"Price: ₹{last_price:,.2f}\n"
f"Change: {direction} ₹{abs(change):.2f} ({pct_change:+.2f}%)\n"
f"52W Range: ₹{week_low} – ₹{week_high}"
)
except httpx.HTTPStatusError as e:
return f"Could not fetch data for {symbol}. HTTP {e.response.status_code}. Check if the symbol is correct."
except Exception as e:
return f"Error fetching stock data for {symbol}: {str(e)}"
@mcp.tool()
async def get_company_info(symbol: str) -> str:
"""Get company fundamentals: sector, industry, market cap, P/E ratio, and listing info.
Use this when asked about what a company does, its sector, or fundamental data.
Symbol examples: RELIANCE, TCS, INFY, HDFCBANK."""
try:
symbol = symbol.upper().strip()
data = await fetch_nse(f"/quote-equity?symbol={symbol}")
meta = data.get("metadata", {})
price_data = data.get("priceInfo", {})
market_cap = meta.get("pdSectorPe", "N/A")
sector = meta.get("pdSectorInd", "N/A")
series = meta.get("series", "EQ")
isin = meta.get("isin", "N/A")
face_value = price_data.get("basePrice", "N/A")
# Get additional security info
sec_data = await fetch_nse(f"/company-corporate-announcements?index={symbol}&from_date=&to_date=")
return (
f"Company: {meta.get('companyName', symbol)}\n"
f"Symbol: {symbol} | Series: {series} | ISIN: {isin}\n"
f"Sector: {sector}\n"
f"Sector P/E: {market_cap}\n"
f"Face Value: ₹{face_value}"
)
except Exception as e:
return f"Error fetching company info for {symbol}: {str(e)}"
@mcp.tool()
async def search_stocks(query: str) -> str:
"""Search NSE-listed stocks by company name or partial symbol.
Use this when the user mentions a company name but you're not sure of the exact symbol.
Returns up to 10 matching companies with their symbols."""
try:
query = query.strip()
data = await fetch_nse(f"/search-autocomplete?q={query}")
results = data.get("symbols", [])
if not results:
return f"No stocks found matching '{query}'. Try a different spelling or the BSE symbol directly."
lines = [f"Stocks matching '{query}':"]
for item in results[:10]:
symbol = item.get("symbol", "")
name = item.get("symbol_info", symbol)
lines.append(f" {symbol}: {name}")
return "\n".join(lines)
except Exception as e:
return f"Error searching stocks: {str(e)}"
if __name__ == "__main__":
mcp.run()
Save this as bse_server.py. The full file is 96 lines including comments and blank lines.
A few implementation notes:
- NSE's API requires a valid cookie from the homepage visit, which is why
fetch_nse()does a homepage request first - The
@mcp.tool()docstrings are written to guide Claude's decision-making ("Use this when asked about...") - Every tool returns a string — FastMCP handles serialisation
- Errors return descriptive messages rather than raising exceptions, so Claude can communicate the issue gracefully
Connecting it to Claude Code
Claude Code uses ~/.claude/claude_desktop_config.json (same format as Claude Desktop). Create or edit it:
{
"mcpServers": {
"bse-data": {
"command": "python",
"args": ["/absolute/path/to/bse_server.py"],
"env": {}
}
}
}
Use the absolute path — relative paths don't work here. Find yours with pwd in the directory where you saved the file.
Restart Claude Code (or Claude Desktop) and you should see "bse-data" in the MCP tools panel.
Test it by asking Claude:
- "What's the current price of Reliance Industries?"
- "Search for HDFC stocks"
- "Tell me about the TCS company — sector and fundamentals"
Claude will call your tools automatically. You don't specify which tool to use — the descriptions handle that.
Adding a resource: portfolio as read-only context
Resources are for data Claude should be able to read on demand, rather than functions it calls with parameters. A portfolio CSV is a good example — you want Claude to be able to reference "my holdings" without you pasting them into every message.
import csv
import os
from mcp.server.fastmcp import FastMCP, Resource
mcp = FastMCP("bse-data-server")
# ... (tools from above)
@mcp.resource("portfolio://holdings")
async def get_portfolio() -> str:
"""The user's current stock portfolio with quantity and average buy price.
Read this when the user asks about their holdings, portfolio value, or P&L."""
portfolio_path = os.path.expanduser("~/portfolio.csv")
if not os.path.exists(portfolio_path):
return "No portfolio file found at ~/portfolio.csv"
with open(portfolio_path, "r") as f:
reader = csv.DictReader(f)
lines = ["Symbol | Quantity | Avg Buy Price"]
lines.append("-" * 40)
for row in reader:
lines.append(f"{row['symbol']} | {row['quantity']} | ₹{row['avg_price']}")
return "\n".join(lines)
Create ~/portfolio.csv:
symbol,quantity,avg_price
RELIANCE,10,2450.00
TCS,5,3800.00
INFY,20,1650.00
Now you can ask Claude "How is my portfolio doing today?" and it will read the resource, then call get_stock_price for each holding.
Tools vs resources: Use tools when you need to pass parameters (look up a specific symbol, search by query). Use resources when the data is a fixed context Claude should have access to (your portfolio, your watchlist, your API config).
What to build next with MCP
Now that you have the pattern, here are three more Indian API servers worth building:
Naukri job search server — Naukri has unofficial APIs that return job listings. Build search_jobs(title, location, experience) and get_job_details(job_id). You can then ask Claude to analyse job trends or find roles matching your skills.
Zerodha portfolio server — Zerodha's Kite API is well-documented and gives you holdings, P&L, order history. Build tools on top: get_holdings(), get_pnl_summary(), get_order_history(days). Then ask Claude to summarise your week or identify tax harvesting opportunities.
GST verification server — The GST portal has a public API to verify GSTIN numbers. Build verify_gstin(gstin) that returns company name, registration status, and state. Useful for anyone running a business who gets invoices from vendors.
💡 Learn the full MCP pattern — the AI Agents track covers function calling, agent design, and multi-agent systems. The function calling lesson is the conceptual foundation for understanding why MCP works the way it does.
Next steps
- What is MCP protocol — the conceptual overview of why MCP exists and how it differs from standard function calling
- MCP complete guide — deeper dive into the full protocol spec
- Build your first AI agent — if MCP tools whetted your appetite for agentic patterns
- Function calling lesson — the foundational concept behind all tool use



