Originally published on medium.com

Build an AI financial agent that combines EODHD fundamentals, prices, and news sentiment to score real stock opportunities, with Python code included.
Most people building financial AI agents connect four or five APIs by hand.
One for prices. One for fundamentals. One for news. One for earnings dates.
The problem isnât lack of data. Itâs fragmentation.
If youâre:
this matters.
Hereâs how to build an AI financial agent that pulls all four together, using EODHDâs financial data API as the backbone.
Hereâs what building a financial agent usually looks like with a plain REST API.
You write a function to fetch prices. Another to fetch fundamentals. Another for news. Then you write orchestration logic so your agent knows which endpoint to call, in what order, and how to merge the responses into something the model can reason about.
Each new data type means a new wrapper function.
Each wrapper function is a new failure point.
And every time the agent needs to answer a question that spans two data types, like âis this companyâs fundamentals strong enough to justify holding through earnings,â youâre the one stitching the context together, not the model.
Developers usually discover this the hard way. The agent works fine in a demo with one data source. Then a real question comes in that needs prices, fundamentals, and news at once, and the whole orchestration layer has to be rebuilt.
Financial agents donât need more APIs.
They need a way to reach every data type through one consistent interface, so the model can decide what to call and combine it, instead of you hardcoding that logic in advance.
Thatâs what MCP (Model Context Protocol) is built for.
MCP (Model Context Protocol) standardizes how an AI model discovers and calls external tools. Instead of you writing a custom wrapper for every endpoint, the model sees a list of available tools with their schemas, and decides which ones to call based on the question itâs answering. Itâs the same underlying idea as Claude function calling, just extended to a whole catalog of tools instead of one function at a time.
For financial data, this matters because a single question rarely maps to a single endpoint.
EODHD ships an official MCP server with over 70 tools covering fundamentals, historical and real-time prices, earnings calendars, news, and sentiment. You point your agent at the server, and it gets access to all of it without you writing a single API wrapper.
Skip the wrapper functions
EODHDâs MCP server exposes 70+ financial data tools out of the box, ready for Claude and other AI agents.
â Get your EODHD API key
Letâs build an agent that can answer a question spanning multiple data types in a single call: fundamentals, upcoming earnings, and recent price action for a given ticker.
pip install anthropic
Youâll need an EODHD API token and an Anthropic API key. EODHDâs MCP server is available as a hosted endpoint, so thereâs no server to run yourself.
EODHDâs MCP server has two versions: v1 takes your API key directly in the URL, v2 uses OAuth 2.1 for clients that support it (like Claude Desktop). For a server-side script like this one, v1 is simpler.
import anthropic
client = anthropic.Anthropic(api_key="YOUR_ANTHROPIC_API_KEY")
EODHD_API_KEY = "YOUR_EODHD_API_TOKEN"
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1500,
messages=[
{
"role": "user",
"content": (
"Give me a quick pre-earnings health check on AAPL: "
"key fundamentals, the next earnings date, and how the "
"stock has moved over the last month."
)
}
],
mcp_servers=[
{
"type": "url",
"url": f"https://mcp.eodhd.com/v1/mcp?apikey={EODHD_API_KEY}",
"name": "eodhd-mcp"
}
]
)
for block in response.content:
if block.type == "text":
print(block.text)
The mcp_servers parameter is the whole trick. You're not calling three endpoints and merging the results yourself. You're handing the model a connection to EODHD's tool catalog and letting it decide what it needs.
For this one prompt, the model will typically:
Three tool calls, one conversation, zero orchestration code written by you.
The API returns a mix of content blocks: text for the model's answer, mcp_tool_use for each tool it called, and mcp_tool_result for the raw data. If you want to inspect what the agent actually pulled (useful for debugging or logging), filter by block type instead of assuming a fixed order:
tool_calls = [
{"name": b.name, "input": b.input}
for b in response.content
if b.type == "mcp_tool_use"
]
for call in tool_calls:
print(call["name"], call["input"])
This is worth doing early. It shows you exactly which tools the model reached for, which helps you spot when itâs calling something unnecessary or missing a data type your prompt implied.
The MCP agent above decides on its own which EODHD tools to call. Thatâs convenient, but it also hides whatâs happening. If you want to actually understand (and customize) the logic behind âwhich stocks look like good opportunities,â it helps to call the same EODHD endpoints directly and build the scoring yourself.
This is also useful if you want a scheduled script that scans the market every morning instead of waiting for you to ask a chat agent.
Letâs build that: a short pipeline that pulls candidates from the Screener API, pulls fundamentals and recent price action for each one, checks the news sentiment, and combines all of it into a single opportunity score.
Instead of pulling fundamentals for thousands of tickers, start narrow. The Screener API filters the entire market down to a shortlist in one request.
import requests
API_TOKEN = "YOUR_EODHD_API_TOKEN"
def get_candidates():
url = "https://eodhd.com/api/screener"
params = {
"api_token": API_TOKEN,
"sort": "market_capitalization.desc",
"filters": '[["market_capitalization",">",1000000000],'
'["exchange","=","us"],'
'["sector","=","Technology"]]',
"limit": 20,
"fmt": "json",
}
response = requests.get(url, params=params)
return response.json()["data"]
candidates = get_candidates()
tickers = [c["code"] for c in candidates]
print(tickers)
This pulls US tech stocks above $1B market cap, sorted by size. Swap the filters for whatever criteria define a âgood priceâ to you: low P/E, high dividend yield, positive EPS growth, or a 52-week low signal.
Once you have a shortlist, fetch the fundamentals for each ticker. This is where you check whether the price is actually backed by solid financials, not just cheap for a reason.
def get_fundamentals(symbol):
url = f"https://eodhd.com/api/fundamentals/{symbol}.US"
params = {"api_token": API_TOKEN, "fmt": "json"}
data = requests.get(url, params=params).json()
highlights = data.get("Highlights", {})
return {
"pe_ratio": highlights.get("PERatio"),
"peg_ratio": highlights.get("PEGRatio"),
"profit_margin": highlights.get("ProfitMargin"),
"eps_growth": highlights.get("EPSEstimateNextYear"),
}
Fundamentals tell you if a company is healthy. Price history tells you if the market has already priced that in, or if thereâs a gap worth paying attention to.
import pandas as pd
from datetime import date, timedelta
def get_price_trend(symbol):
url = f"https://eodhd.com/api/eod/{symbol}.US"
params = {
"api_token": API_TOKEN,
"period": "d",
"order": "d",
"from": (date.today() - timedelta(days=90)).isoformat(),
"fmt": "json",
}
prices = requests.get(url, params=params).json()
df = pd.DataFrame(prices)
last_close = df.iloc[0]["close"]
month_ago_close = df.iloc[21]["close"] if len(df) > 21 else df.iloc[-1]["close"]
change_pct = ((last_close - month_ago_close) / month_ago_close) * 100
return {"last_close": last_close, "change_30d_pct": round(change_pct, 2)}
A stock with strong fundamentals that just dropped 15% in a month is a very different opportunity than one that already ran up 40%.
Price and fundamentals donât tell you why a stock moved. Sentiment fills that gap, and itâs often what separates a real opportunity from a value trap.
def get_sentiment(symbol):
url = "https://eodhd.com/api/sentiments"
params = {
"s": f"{symbol}.US",
"api_token": API_TOKEN,
"from": (date.today() - timedelta(days=14)).isoformat(),
"fmt": "json",
}
data = requests.get(url, params=params).json()
entries = data.get(f"{symbol}.US", [])
if not entries:
return {"avg_sentiment": None}
avg = sum(e["normalized"] for e in entries) / len(entries)
return {"avg_sentiment": round(avg, 3)}
This is the part a raw API canât do for you. Each endpoint gives you one dimension. Deciding what a âgood opportunityâ means is a judgment call, and it belongs in your code, not buried in someone elseâs black-box score.
def score_opportunity(symbol):
fundamentals = get_fundamentals(symbol)
price = get_price_trend(symbol)
sentiment = get_sentiment(symbol)
score = 0
if fundamentals["pe_ratio"] and fundamentals["pe_ratio"] < 25:
score += 1
if fundamentals["profit_margin"] and fundamentals["profit_margin"] > 0.10:
score += 1
if price["change_30d_pct"] < -5:
score += 1
if sentiment["avg_sentiment"] and sentiment["avg_sentiment"] > 0.15:
score += 1
return {
"symbol": symbol,
"score": score,
**fundamentals,
**price,
**sentiment,
}
results = [score_opportunity(t) for t in tickers]
ranked = sorted(results, key=lambda r: r["score"], reverse=True)
for r in ranked[:5]:
print(r["symbol"], "score:", r["score"], "| P/E:", r["pe_ratio"],
"| 30d change:", r["change_30d_pct"], "%",
"| sentiment:", r["avg_sentiment"])
Four data points, one loop, one ranked list. Reasonable valuation, healthy margins, a recent dip, and improving sentiment together are a much stronger signal than any one of them alone.
This scoring logic is intentionally simple. Youâll want to weight it differently depending on your strategy: a value investor cares more about P/E and margins, a swing trader cares more about the price drop and sentiment shift. The point is that the four EODHD endpoints give you the raw material, and the decision logic is yours to tune.
None of this is financial advice. Itâs a framework for turning scattered data into a shortlist worth researching further, not a signal to buy.
Once this scoring pipeline works as a standalone script, you can expose it to your MCP agent as a custom tool alongside EODHDâs built-in ones. Then a prompt like âfind me tech stocks that dropped recently but still look fundamentally solidâ runs your exact scoring logic instead of the model guessing at criteria on its own.
Thatâs the real advantage of combining direct API calls with MCP: you get full control over the decision logic, and the model still handles the natural-language layer on top of it.
This is, in practice, what it means to build an AI financial agent: not a single clever prompt, but a data layer you trust plus a model that knows when to reach for it.
Once the agent can combine fundamentals, prices, earnings, and news on its own, the use cases stop being single-question demos.
None of this requires new wrapper code. It requires better prompts and, occasionally, a narrower system prompt telling the agent which tools to prioritize.
â Do I need to run my own MCP server to use EODHDâs tools?
â
No. EODHD hosts the MCP server, so you connect to it with a URL and your API token, the same way youâd call a REST endpoint.
â Does MCP replace the EODHD REST API?
â
No, it sits on top of it. The REST API still powers every tool call, MCP just gives the AI model a structured way to discover and use those endpoints without custom wrapper code.
â Can I limit which tools the agent has access to?
â
Yes. You can scope the system prompt or restrict the conversation to specific tool categories if you donât want the agent reaching for endpoints outside a given use case, like keeping it to fundamentals and earnings only.
â Is this approach only useful with Claude?
â
No. MCP is an open protocol, so EODHDâs server works with any MCP-compatible client, including ChatGPT, Cursor, and Windsurf, not just Claude.
â How many API calls does a scan like this use?
â
It depends on your shortlist size. Each screener request counts as 5 calls, and each fundamentals, price, or sentiment call for a ticker counts as 1. Scanning 20 candidates works out to roughly 65 calls, well within EODHDâs free tier for occasional runs.
â Can I use this scoring script to automate actual trades?
â
The script only ranks and prints candidates. Connecting it to a brokerâs execution API is a separate step, and itâs worth adding manual review before any live-money decision, no matter how the shortlist was generated.
If youâre a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on LinkedIn.
Want more content like this?
Python, APIs, fintech, and AI agent tutorials for developers.
â Visit kevinmeneses.com
Looking for technical content for your company? I can help â LinkedIn · kevinmenesesgonzalez@gmail.com