A while back I was building a little research agent — nothing fancy, just an LLM that could answer questions about markets so I didn't have to keep five broker tabs open. The first time I asked it "what's Tesla trading at right now?" it answered instantly and confidently: $248.50.
The problem? The actual price that afternoon was nowhere near that. The model wasn't reading a quote. It was pattern-matching on numbers it had seen during training and handing me a plausible-looking hallucination with a straight face.
This is the thing nobody tells you when you start wiring LLMs into real workflows: a language model has no idea what any price is right now. Its weights froze on its training cutoff. Ask it for a live number and it will either refuse or, worse, make one up. For anything time-sensitive — prices, weather, inventory, your own database — the model is only as good as the tools you hand it.
So I gave mine a tool. This post is how I did it, two ways: the five-minute version using an MCP server, and the "build it yourself" version with plain function calling. Code is Python, but the pattern is identical in any language.
Modern LLMs support tool use (also called function calling). You describe a function — its name, what it does, its parameters — and the model, instead of answering from memory, can decide to call that function. Your code runs it, hands the result back, and the model writes its final answer grounded in real data.
The loop looks like this:
User: "What's NVIDIA trading at?"
→ Model decides: call get_price(symbol="NVDA.US", market="stock")
→ Your code hits a market-data API, gets 183.22
→ Model receives 183.22
→ Model: "NVIDIA is currently trading at $183.22."
The model never invents the number. It asks for it. That single architectural change is the difference between a toy and something you'd actually trust.
The only missing piece is a data source that returns a real-time quote from a plain HTTP call. I ended up using Infoway because one API key covers US stocks, HK, A-shares, Japan, India, crypto, forex, and commodities — so my one get_price tool works across every market without me stitching together three vendors. Any real-time feed works; the pattern below is what matters.
Path A: The five-minute version (MCP server)
If you're using Claude Desktop, Cursor, or Claude Code, you don't have to write any of the tool-calling code. The Model Context Protocol (MCP) is an open standard for exposing tools to AI assistants, and Infoway ships an official MCP server that registers 17 financial-data tools — real-time quotes, candlesticks, market breadth, sector heatmaps, company fundamentals — in one shot.
Install is a single command:
uvx infoway-mcp-server
Then point your assistant at it. For Claude Desktop, edit claude_desktop_config.json (on macOS: ~/Library/Application Support/Claude/, on Windows: %APPDATA%\Claude\):
{
"mcpServers": {
"infoway": {
"command": "uvx",
"args": ["infoway-mcp-server"],
"env": {
"INFOWAY_API_KEY": "your_api_key_here"
}
}
}
}
The config is identical for Cursor (.cursor/mcp.json) and Claude Code (.claude/settings.json). Restart the client and you can just talk:
"What are Apple and Tesla trading at right now?" → the assistant calls get_realtime_trade with AAPL.US,TSLA.US
"Show me Bitcoin's daily candles for the last 30 days." → it calls get_kline for BTCUSDT
"How's the US market doing today, and which sectors are leading?" → it calls get_market_temperature and get_leading_industries
No glue code, no loop to maintain. This is genuinely the right answer if you just want your existing AI client to stop guessing. But if you're building your own agent — an app, a Slack bot, a backend service — you need the next part.
Here's the same capability wired into your own agent from scratch. I'll use the Anthropic SDK because I find its tool-use API clean, but the shape is the same on any provider that supports function calling.
Step 1: The actual data fetch
First, the boring-but-critical part — a function that gets a real quote. Infoway's trade endpoint is a plain GET; you comma-separate symbols in the path and pass your key as a header. Different asset classes live under different path prefixes (/stock/, /crypto/, /common/ for forex and commodities), so I route on a market argument:
import requests
INFOWAY_KEY = "your_api_key_here"
MARKET_PREFIX = {
"stock": "stock", # AAPL.US, 700.HK, 600519.SH
"crypto": "crypto", # BTCUSDT, ETHUSDT
"forex": "common", # EURUSD, USDJPY
}
def get_price(symbol: str, market: str = "stock") -> dict:
"""Return the latest trade price for one symbol."""
prefix = MARKET_PREFIX.get(market, "stock")
url = f"https://data.infoway.io/{prefix}/batch_trade/{symbol}"
headers = {"apiKey": INFOWAY_KEY, "Accept": "application/json"}
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
payload = resp.json()
if payload.get("ret") != 200 or not payload.get("data"):
return {"symbol": symbol, "error": payload.get("msg", "no data")}
quote = payload["data"][0]
return {
"symbol": quote["s"],
"price": float(quote["p"]),
"timestamp_ms": quote["t"],
}
Quick sanity check before you let a model anywhere near it:
>>> get_price("NVDA.US", "stock")
{'symbol': 'NVDA.US', 'price': 183.22, 'timestamp_ms': 1781672609412}
>>> get_price("BTCUSDT", "crypto")
{'symbol': 'BTCUSDT', 'price': 96450.1, 'timestamp_ms': 1781672610233}
The model needs a schema so it knows when and how to call your function. Be specific in the description and, crucially, spell out the symbol format — this is where most agents trip up:
TOOLS = [
{
"name": "get_price",
"description": (
"Get the current real-time trade price for a stock, "
"cryptocurrency, or forex pair. Use this whenever the user "
"asks about a current or latest price. Never guess a price."
),
"input_schema": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": (
"Ticker in Infoway format. US stocks: AAPL.US. "
"HK: 700.HK. A-shares: 600519.SH / 000001.SZ. "
"Crypto: BTCUSDT. Forex: EURUSD."
),
},
"market": {
"type": "string",
"enum": ["stock", "crypto", "forex"],
},
},
"required": ["symbol", "market"],
},
}
]
That "Never guess a price" line in the description is doing real work. Paired with a system prompt (below), it's what keeps the model from falling back on its old hallucinating habits.
Step 3: The agent loop
Now the loop that ties it together. The model may want to call the tool, so you keep going until it stops asking:
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from env
SYSTEM = (
"You are a market assistant. You do not know any live prices from "
"memory. Whenever a user asks about a current price, you MUST call "
"get_price and answer only from its result. Never state a price you "
"did not fetch."
)
def run_agent(question: str) -> str:
messages = [{"role": "user", "content": question}]
while True:
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=SYSTEM,
tools=TOOLS,
messages=messages,
)
if resp.stop_reason != "tool_use":
# Model is done — return its text answer.
return "".join(b.text for b in resp.content if b.type == "text")
# Model asked for one or more tool calls. Run them.
messages.append({"role": "assistant", "content": resp.content})
results = []
for block in resp.content:
if block.type == "tool_use":
out = get_price(**block.input)
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(out),
})
messages.append({"role": "user", "content": results})
print(run_agent("Is ETH above 3000 right now? And what's Apple at?"))
Run it and you'll see the model fire off two get_price calls — one for ETHUSDT, one for AAPL.US — then answer with numbers that are actually current:
ETH is at $3,142.08, so yes, it's above 3,000. Apple is trading at $228.51.
That's the whole thing. Roughly 60 lines, one real function, and your agent went from confidently wrong to actually correct.
The parts that bit me (learn from my mistakes)
A working demo and a reliable agent are two different animals. Here's what I got wrong the first time:
1. Symbol format is a silent failure. The API doesn't throw if you send AAPL instead of AAPL.US — it just returns empty data, and the model gets nothing useful. I now put the exact format in the tool description and keep a small alias map ("apple" → "AAPL.US") so the model doesn't have to be perfect. Watch A-shares especially: 600519.SH for Shanghai, 000001.SZ for Shenzhen.
2. The model will still try to guess if you let it. Without the strict system prompt, I caught it "helpfully" answering from memory when a tool call timed out. Make refusal-to-guess an explicit instruction, and treat any tool error as a reason to say "I couldn't fetch that," not to improvise.
3. Markets close. A stock quote from a closed exchange is the last trade, not a live one — that's correct behavior, but your agent should know the difference. The timestamp is right there in the response (timestamp_ms); I feed it to the model so it can say "as of Friday's close" instead of implying the market is open. Crypto and forex don't have this problem (24/5 and 24/7 respectively), which is another reason I like having them behind the same tool.
4. Rate limits are real. Free tiers cap requests (Infoway's free plan is 60/minute). If your agent fans out across a watchlist, batch the symbols — the batch_trade endpoint takes a comma-separated list in one call — instead of looping one request per ticker.
Where this goes next
Once you have one tool wired in, adding more is trivial — the loop doesn't change, you just append to TOOLS. The natural next steps are a get_candles tool for "how has this moved this week," a get_fundamentals tool for valuation questions, or a market-breadth tool for "what's the mood today." (That's exactly the set the MCP server exposes out of the box, if you'd rather not hand-roll each one.)
But the lesson generalizes way past finance. Any time you want an LLM to reason about the present — your database, an internal API, live sensor data — the recipe is the same: stop expecting the model to know, and start giving it the ability to look up. The model's job isn't to be a database. It's to decide when to query one.
If you want to try the market-data version, Infoway has a 7-day free trial with no credit card, which is enough to build and test the whole thing. Grab a key, drop it into either path above, and watch your agent stop making up prices.
FAQ
Does this work with OpenAI / Gemini / open models instead of Claude?
Yes. Every major provider supports function calling with the same three-step shape — describe the tool, let the model request it, feed back the result. Only the SDK method names and the tool-schema field names differ. The get_price function itself doesn't change at all.
Do I need the MCP server and the custom loop, or just one?
Just one. Use the MCP server if you want tools inside an existing assistant (Claude Desktop, Cursor, Claude Code) with zero code. Write the loop if you're building your own app or service where you control the agent.
Why not just let the model browse the web for prices?
Web browsing is slow, flaky, and you're scraping a rendered number you can't validate. A typed API call returns structured data in tens of milliseconds with a real timestamp — far more reliable for anything a user might act on.
How do I stop the model from hallucinating a price when the tool fails?
Two things: a system prompt that forbids stating any un-fetched price, and error handling that returns an explicit error object to the model (not a fabricated fallback). When the model sees an error in the tool result, it'll tell the user it couldn't fetch — which is the honest answer.
Can one tool really cover stocks, crypto, and forex?
Yes, as long as your data source does. That's why I route on a market parameter and use a provider with unified coverage — the model picks the market, the code picks the endpoint, and you maintain one function instead of three integrations.
What's the latency like?
The REST quote call is a single round trip — typically well under a second including the model's turn. If you need sub-100ms streaming (a trading terminal, a live alert engine), switch the data layer to a WebSocket feed; the tool-use pattern on top stays exactly the same.