Inspiration

A few months ago I tried to build a fintech tool that would parse personal bank statements. I sat down with a PDF of my own bank statement and pdfplumber, expecting maybe an afternoon of work. It took me a full day to even get the rows out cleanly, and by the end I was looking at a column of merchant names that read like a different language for every transaction type. That experience taught me how messy financial data is.

So when I saw the Fivetran track, I recognised the other end of the problem. If clean financial data was a solved problem, the next layer up was the question: what would a small business actually want to do with it?

Small e-commerce founders run their financial life across Shopify, Stripe, a bank, and a spreadsheet that's always three weeks out of date. A bookkeeper costs around £40,000 a year. A part-time CFO is £80,000+. Most small businesses can't afford either, so they go without, and silently lose money to duplicate charges, customer concentration, and subscription drift.

LedgerAgent is what happens when you give that vigilance to an agent instead.

What it does

A founder opens the page and asks "are there any problems in my finances I should know about?"

LedgerAgent plans a multi-step investigation. It pulls recent transactions from BigQuery, runs a duplicate-detection analysis, then a revenue-concentration analysis. The reasoning shows up live in the chat, including every tool call.

In the demo data, it catches two planted stories:

  1. A duplicate ShipBob charge: TX002 on April 1 and TX011 on April 8, both for £892.40. Normal ShipBob billing is monthly, so two charges 7 days apart is suspicious. The agent cites the IDs and offers to draft a dispute.

  2. Customer concentration risk: Beauty Box Co accounts for £17,000 of revenue (25.9%) from just 2 orders. The agent distinguishes this from Shopify and Stripe payouts (which aggregate many small sales) and correctly flags only the wholesale customer.

Then the agent takes action. When asked to draft a dispute, it produces a properly-formatted email that is addressed to ShipBob, citing both transaction IDs, written in the user's voice. The drafted email renders as a card in the chat with Edit and Send buttons. When the user clicks Send, the agent calls a real email API (Resend) and delivers the dispute to the user's verified inbox, ready to forward to the vendor with one click.

It also operates on the data pipeline itself. Ask "is my data fresh?" and it calls Fivetran's pipeline status. Say yes to a sync trigger and it actually triggers one.

How I built it

Four layers, all live, no mocks. Solo.

Data layer. Fivetran syncs a Google Sheet into BigQuery on a schedule. The dataset is ledger_demo.transactions with 60 rows of demo data covering April-May 2026.

Tool layer — two MCP servers. Per the hackathon's Fivetran resources page (https://rapid-agent.devpost.com/details/fivetran-resources), there are two sanctioned ways to integrate with Fivetran: their official MCP server, or their REST API. This project does both.

The first MCP server is custom, written from scratch in Python with FastMCP, deployed on Cloud Run. Seven tools: query_recent_transactions, find_duplicate_transactions, analyze_revenue_concentration, get_pipeline_status, trigger_sync, draft_dispute_email, and send_email. Three are write operations. Credentials in Google Secret Manager. The server talks to BigQuery via SQL, Fivetran via REST API, and Resend via their Python SDK. The send_email tool enforces a hardcoded allowlist of recipient addresses — even if the agent hallucinates a recipient, the email won't go to the wrong person.

The second MCP server is Fivetran's official open-source one (github.com/fivetran/fivetran-mcp), vendored unchanged into the agent service container and run as a stdio subprocess. It exposes Fivetran's full REST API surface as a unified set of MCP tools — list connections, get sync status, inspect destinations, etc. This gives the agent direct, canonical access to Fivetran's MCP server alongside the composed custom integration. Belt and suspenders.

Reasoning layer. Google's Agent Development Kit (ADK) wraps Gemini 3 with two McpToolset instances — one pointed at the custom MCP server (HTTP), one running Fivetran's MCP server as a stdio subprocess. ADK aggregates both toolsets so Gemini sees them as a unified toolkit. The agent service is wrapped in FastAPI to expose a /chat endpoint that streams responses via Server-Sent Events, deployed on Cloud Run too, talking to Gemini through Vertex AI. The system prompt teaches the agent which MCP server to use for which task (BigQuery analytics and emails on the custom one, pipeline operations on Fivetran's) and the right chain-of-action: investigate → draft → confirm → send.

Interface layer. A single-page landing site in hand-written HTML, CSS, and JavaScript (no framework). Vercel hosts it. The chat UI parses the SSE stream and renders tool calls as they happen, then the final answer. When the agent calls draft_dispute_email, the frontend detects the tool result and renders a dedicated email card with Edit/Send buttons rather than a plain text bubble, making the human-in-the-loop pattern visible.

The architecture diagram in the GitHub README shows the full request flow.

Challenges I ran into

Cold starts and stale MCP sessions. Cloud Run scales to zero. The first time the agent service tried to use a long-lived MCP session after Cloud Run had scaled the MCP container down, ADK got "Session terminated" errors and silently fell back to no-tool responses, which the LLM then hallucinated answers for. Looked like the agent was working, but with completely fake transaction IDs.

Recipient hallucination. Early in the email feature, the agent would correctly draft the dispute email, then when asked to send, invent a fictional recipient like maya@example.com and try to send to that. The allowlist guard correctly refused, but the user experience was bad. Fix was teaching the agent in the system prompt that its verified inbox is hardcoded and it doesn't need to invent a recipient.

Custom MCP server vs the reference. Fivetran's reference MCP is stdio transport with a low-level Server class, not easily portable to HTTP/Cloud Run. So I wrote my own slim MCP server with FastMCP, talking directly to Fivetran's REST API. Less generality, but cleaner deploy and full control of what tools the agent gets.

Frontend tool-result rendering. When the agent calls draft_dispute_email, I wanted the frontend to render a dedicated email card instead of plain text. That required surfacing the structured tool return in the SSE stream (ADK's default behaviour just passes the tool name, not the result), then unwrapping the FastMCP envelope (result.structuredContent) on the frontend.

Accomplishments I'm proud of

Everything in the architecture is a real, public deployment.

The agent visibly reasons. Tool calls render in the chat as they happen, before the final answer comes back and then the agent acts, not just talks. Real emails go out via Resend.

The system distinguishes payment processor aggregators from individual wholesale customers. Shopify Payments (44.8% of revenue) and Stripe Payments (29.4%) aren't flagged as concentration risk, but Beauty Box Co (25.9%) is. That nuance came out of careful system prompt design, not a hardcoded rule.

Building this solo, end to end, from limited GCP knowledge to a deployed multi-service AI agent system with a real email-sending capability.

What I learned

MCP is a real protocol, not just a buzzword. Building my own MCP server forced me to understand the spec, and that paid off when deploying it standalone.

Agents are not chatbots. The framing "agent that operates, not one that just answers" became the design principle for what tools to write. I deliberately added send_email as a real write operation with a safety allowlist to prove the distinction.

Cloud Run + ADK + custom MCP works, but the integration is new enough that the failure modes (cold starts, session lifecycle) aren't well documented.

Safety design matters even in demos. The recipient allowlist for send_email started as a self-defense against the agent hallucinating, but it became a real product principle: never let an autonomous system send anywhere unverified. The agent drafts; the human reviews; the email goes only to the operator's own inbox for them to forward.

What's next for LedgerAgent

Production deploy. Set Cloud Run min-instances=1 to eliminate cold-start risk during judging.

More tools. Categorisation, anomaly detection on time-series, draft-and-send for refund requests and supplier disputes. Each tool is a small, focused capability.

Real domain verification. Right now emails send from onboarding@resend.dev (Resend's sandbox). With a verified domain, the agent could send from dispute@maya-skincare.com directly to the vendor.

Real customers. The persona (Maya) is fictional, but the problem is not. Talking to actual small e-commerce founders is the next step.

Built With

  • bigquery
  • fastapi
  • fastmcp
  • fivetran
  • gemini-3
  • google-adk
  • google-cloud-run
  • javascript
  • model-context-protocol
  • python
  • resend
  • vercel
  • vertex-ai
Share this project:

Updates