FDDB Exporter: A Web UI, New Views, and an MCP Server

Published 2 days ago

It’s been a while since I last wrote about FDDB Exporter, and quite a lot has changed since the MongoDB redesign. The short version: the app is no longer a scraper with an API bolted on. It now ships its own frontend, has a proper set of analysis views, dropped the old API, and — the part I’m most happy with — can hand your entire nutritional diary to an AI assistant over MCP.

The Web UI Replaces the Flutter App

With version 2.0.0 the application serves a built-in web UI at the application root, so http://localhost:8080/ now gives you a graphical interface instead of an error page. It covers everything the API exposes: exporting data, browsing entries, searching products, correlation analysis, statistics and downloads in CSV or JSON.

Which means the Flutter app is deprecated. It still works against version 1.7.0, but it will not be updated for 2.x and beyond. I’m not particularly sad about it. The app was fun to build and it did solve my “I forgot to log two days again” problem, but maintaining a separate Flutter codebase, building APKs and shipping releases for a frontend that only ever talked to my own backend was a lot of ceremony for very little benefit.

The web UI is fully responsive and can be installed as a Progressive Web App. On mobile you use the browser’s share menu and pick “Add to Home Screen”, on desktop there’s an install icon in the address bar. Afterwards it launches like any other app, without browser chrome, and loads noticeably faster. You need HTTPS for the install prompt to show up, so if you run this behind a reverse proxy with a certificate (which you should anyway) you get it for free.

So the end result is the same icon on my home screen as before, except now there’s no separate project to maintain and it works on every platform with a browser rather than only on Android.

Version 2.3.0 restructured the navigation and added the views I had been missing.

Trends is the new one. It charts a single metric — calories, fat, carbs, sugar, protein or fibre — over a date range, bucketed by day, week or month. There are quick-select ranges and a summary of highs, lows and the change over the period. This is the view that finally answers “am I actually eating more protein than I did in spring, or does it just feel that way?” without exporting to InfluxDB and building a Grafana dashboard for it. That option obviously still exists, but for a quick look it’s nice to stay in the app.

Products combines two things that used to be scattered across endpoints. The Explorer gives you a per-product history with a weekday distribution and autocomplete search, and Top Products ranks what you eat most, either by frequency or by how much it contributes to calories, fat, carbs or protein. Seeing which five products are responsible for most of my carbs was mildly humbling.

Entries is the former Data Query view, renamed and extended. It can now browse a full date range instead of a single day and includes a Missing Days list showing gaps in your logging. Related to that, the stats endpoint now reports current and longest logging streaks, the most recent entry date and the missing days since you started tracking. The Rolling Averages view also gained a by-day-of-week table, and its macro distribution is now kcal-weighted (fat at 9 kcal/g, carbs and protein at 4) rather than by gram, which gives a far more honest picture of where the energy actually comes from.

Goodbye v1 API

The v1 API was deprecated in 1.7.0 with removal announced for after 2026-06-30, and with 2.4.0 it is gone. If you script against the exporter, switch your base path from /api/v1 to /api/v2. Two endpoints also moved while I was at it:

  • /api/v1/fddbdata/migrateToInfluxDb/api/v2/migration/toInfluxDb
  • /api/v1/fddbdata/stats[/averages]/api/v2/stats[/averages]

One thing to watch out for: calls to /api/v1/* don’t return a helpful 410 or anything of the sort, they return the web UI’s HTML. The frontend is served at the root and now catches those paths. So if your script suddenly starts choking on an HTML document where it expected JSON, that’s your answer. The web UI and the MCP server are unaffected.

While on the topic of API behaviour: exports no longer run in parallel. The scheduler, the REST API, the web UI and the MCP export tools all share a single lock, so one account is never logged in and scraped twice at the same time. For scripts this means POST /api/v2/fddbdata and GET /api/v2/fddbdata/export now answer with 409 Conflict if an export is already running. The request is refused, not queued — retry once the running one is done.

The MCP Server

Now for the part I actually wanted to write about.

Since 2.3.0 the exporter can run an MCP server, which means you can point Claude Desktop, Claude Code or any other MCP client at your diary and just ask questions. Not “construct the right query parameters for the correlation endpoint and interpret the JSON”, but:

How much protein did I average last month compared to the month before?

How often do I eat oats, and on which weekdays?

I had migraines on these five dates — is there anything I ate consistently the day before?

That last one is what the correlation API was always for, but in practice I never used it, because formulating the request and reading the response was more effort than the question was worth. Handing that to an assistant removes the friction entirely, and it can follow up on its own findings instead of returning a list I then have to think about.

What It Exposes

The server ships around twenty tools grouped into a few areas:

  • Diary data: get_day, get_days for a range of up to 366 days, and list_missing_days.
  • Products: search_products, list_top_products, get_product_summary, list_distinct_products and find_days_with_products.
  • Statistics: get_stats, get_averages, get_extreme_days, get_trend, get_weekday_breakdown, get_macro_split, compare_periods and check_goals.
  • Correlation: correlate_products_with_dates, the one described above.
  • Meta: get_data_schema and get_server_info, so the model can find out what the fields actually mean instead of guessing.

There are also resources (fddb://stats, fddb://day/{date} and fddb://schema) that a client can pull in as context without spending a tool call, and a handful of prompts — pre-written workflows you invoke deliberately rather than having them fire automatically: weekly_nutrition_review, find_trigger_foods, protein_gap_analysis and logging_hygiene_check. The weekly review is the one I use most; it compares the last seven days against the previous week and against my history and tells me what actually changed.

A few design decisions worth mentioning, because they matter for the quality of the answers. Results are capped and truncated so responses stay a reasonable size for a language model — with one exception: get_product_summary is uncapped, because an aggregate computed from a truncated list is simply wrong. Empty ranges return found: false rather than an error, so the model doesn’t treat “no data” as “something broke”. Days you never logged are excluded from averages and trends, and the tools report their coverage, so a week with three missing days doesn’t quietly turn into a week of very low calories. Dates accept ISO format as well as aliases like today, yesterday or 13_days_ago, which saves the model from doing date arithmetic — something LLMs are still surprisingly bad at.

Goals for check_goals are supplied per conversation and not stored anywhere. I deliberately did not add a settings screen for target macros; telling the assistant “my target is 150g protein and 2200 kcal” in the conversation is faster and lets you try different numbers without persisting anything.

Optional Write Access

By default the MCP server is read-only. There is a separate group of export tools — export_range (up to 14 days per call), export_days_back and export_missing_days — that let the assistant trigger a scrape of fddb.info itself. Those are behind their own flag, so enabling MCP does not implicitly grant the model the ability to hit an external website on your behalf.

export_missing_days is genuinely convenient though. It is the same “I forgot to log the weekend” problem that made me build a Flutter app three years ago, except now the fix is telling the assistant to fill the gaps and it figures out which days those are.

Setup

The MCP server is disabled by default and requires MongoDB persistence. Enable it with:

FDDB-EXPORTER_MCP_ENABLED=true

And, if you want the export tools as well:

FDDB-EXPORTER_MCP_WRITE-TOOLS-ENABLED=true

It uses streamable HTTP at http://localhost:8080/mcp. For Claude Code, adding it is a one-liner:

claude mcp add --transport http fddb-exporter http://localhost:8080/mcp

For Claude Desktop, the config looks like this:

{
  "mcpServers": {
    "fddb-exporter": {
      "type": "http",
      "url": "http://localhost:8080/mcp"
    }
  }
}

Claude Desktop needs the endpoint reachable from the outside, whereas Claude Code is happy with localhost.

A Word on Security

The MCP endpoint has no authentication of its own. That is a deliberate choice — the application is meant to run in your own network, and bolting on a half-hearted auth mechanism would give a false sense of safety. It also means: do not expose /mcp to the internet without putting authentication in front of it, for example basic auth on your reverse proxy. Anyone who reaches that endpoint can read your entire diary, and with the write tools enabled, log into FDDB on your behalf to scrape more of it.

This is also why the whole thing is off by default. Enable it once you’ve thought about where it’s running.

Other Fixes Worth Knowing

Two things from 2.4.0 that will matter if you’re running an older version:

Every export started failing with an empty HTTP 500 after fddb.info stopped rendering a block above the diary. Sugar and fibre were read by their position on the page, which shifted along with that block. They’re now looked up by their row label instead, which is what I should have done in the first place. A page that can’t be parsed is also reported as an unsuccessful day again rather than aborting the entire export.

And on Kubernetes, wrong FDDB credentials no longer restart the container in a loop. The fddb-login-check was part of the liveness probe group, so an invalid password meant an endless restart cycle. The login status is still reported on /actuator/health and shown in the web UI, it just doesn’t kill the pod anymore.

Wrapping Up

Between 2.0.0 and 2.4.0 the exporter went from “a scraper with a REST API and a companion app” to something that stands on its own: a web UI you install like an app, views that answer the questions I actually have about my data, and an MCP server that lets me ask the rest in plain language.

If you’re upgrading from 1.x, read the changelog first — there are breaking changes in 2.2.0 (MongoDB environment variables changed with Spring Boot 4) and 2.4.0 (v1 API removal). Everything else should just work.

The documentation has the full details, the Helm chart is still on Artifact Hub, and issues and pull requests are welcome as always.

Comments

Comments are hosted on comments.itobey.dev. Loading them sets cookies and shares your IP address with that service.