← Back to work

Multi-Agent Research Pipeline · Jan 2026 to present

FinRobot

Type a stock ticker, get a formatted equity research report. Behind it are three MCP servers running three different model providers, each picked for the one thing it's genuinely best at.

Role
Lead contributor, MCP tooling & services
Team
6 contributors, RIT AI Club
Stack
Python · MCP · FastAPI · React
Code
GitHub ↗

What it does

This is the explanation I use when demoing it, because it's the one that actually lands with people who don't write software:

You type in a stock ticker. One bot goes out and gathers the research. It hands that to the main bot, which decides what's needed and passes the work along: to a graph maker that draws the charts, and to a final bot that formats everything into a clean document. What comes back is a PDF you could actually hand to someone.

Three phases, each handled by something specialised. That framing survives contact with a non-technical audience, and it also happens to be an accurate description of the architecture rather than a simplification of it.

Why MCP, and not just API calls

The honest answer to "why not call the APIs directly in Python" is that we could have; for a single-model pipeline it would have been less code. The reason MCP earns its place here is that this isn't a single-model pipeline.

Research, charting, and formatting are genuinely different problems, and the best tool for each is different. The Model Context Protocol lets each one run as its own server, exposing its capabilities through a consistent interface, so the orchestrator can call them without knowing or caring what's behind them. Swapping the model inside one server doesn't touch the other two.

mcp_servers:
  - name: "research"           # Perplexity: search-native retrieval
  - name: "chart_generation"   # Matplotlib: deterministic, no model
  - name: "formatting"         # Claude: document layout & styling

orchestrator: Gemini           # tool calling and coordination

Model routing by task

The trade-off

Three servers and three providers means three sets of credentials, three failure modes, and a process-management story that a single script wouldn't have. In exchange each phase is independently swappable and testable, and we could replace the formatting model without regression risk to research. For a project whose stated purpose is learning how agent architectures fit together, that's the right side of the trade.

Things that only show up when you run it

Rate limits are an architecture problem

The orchestrator sits in the middle of every request, which makes it the first thing to hit a rate limit. Gemini returning HTTP 429 mid-pipeline doesn't just fail one call; it strands a report halfway through, after the research phase has already been paid for.

Sends now retry with exponential backoff: five attempts, starting at two seconds and doubling. It's a small piece of code that turns a class of hard failure into a slow success, and it only became obviously necessary once several people were demoing simultaneously.

PDF conversion is its own problem

Getting a report to look right in a browser is not the same as getting it to look right on a page. Export runs through Playwright, driving a real browser engine to render the HTML and print it, because that's the only reliable way to get output that matches what you saw on screen.

The charts can't go to the model

The formatting server has to place charts inside a document written by Claude, and the obvious way to do that is to show Claude the charts. That doesn't survive contact with the numbers: a single chart PNG is roughly 67,000 input tokens. Two of them and the request budget is gone before any research text is attached, and a model handed base64 has a habit of trying to hand it back.

So the images never leave the Python side. Claude receives only the chart captions and is told to write CHART_PLACEHOLDER_0, CHART_PLACEHOLDER_1 and so on where each chart belongs; the server swaps those tokens for real <img> tags after generation returns. The model does layout, which is what it's good at, and never touches the bytes.

The failure mode is worth naming: if the model omits a placeholder, nothing errors. You get a valid report with a chart-shaped hole in it, so a missing placeholder now logs a warning rather than passing silently.

It won't chart numbers it doesn't have

Price data comes from yfinance. Early on, a failed fetch fell back to a random walk so the pipeline could keep running, which meant a broken data source produced a report that looked exactly like a working one: confident, formatted, and made up.

That default is now inverted. A failed price fetch stops the run. Synthetic prices require an explicit --allow-synthetic-prices flag, and when they're used the chart is captioned as simulated inside the report itself, not just in a log line nobody reads. A financial document that is silently wrong is worse than no document.

The cleanup nobody demos

After the feature work slowed down I spent a pass on the parts that don't show up in a demo, and it turned up more than I expected.

None of this is interesting to watch. It's also the difference between a project that runs on my machine and one another club member can clone on Monday and have working.

Presenting it

FinRobot was presented at ImagineRIT 2026 to roughly 200–300 attendees across the day, alongside demos at club meetings and tabling events.

The audience at ImagineRIT is deliberately general: students, faculty, industry visitors, and members of the public with no technical background. Explaining a multi-agent MCP pipeline to someone who has never heard of any of those words is a genuinely useful constraint, and it's where the "one bot gathers, one draws, one writes" framing came from. Being forced to describe a system without jargon is a fast way to find out which parts of your own design you don't actually understand.

Where it's going

FinRobot started as an exploration project, a way for the AI Club to learn how agent architectures, MCP, and multi-model pipelines actually behave when you build something real with them. That's still its primary purpose.

The current direction is evolving it toward a practical use case for investors: output someone would genuinely use for research rather than a demonstration that it can be produced. It won't be commercialised. It's a club project, built by club members for learning, and it stays that way.

Six people have contributed to it so far. My work has centred on the MCP tooling and the server-side logic behind it, the layer that has to turn inconsistent, rate-limited, occasionally-disagreeing external sources into something predictable enough for a model to reason over.