Choo Choo

How it was built

One author, one day, a whole ecosystem

Choo Choo is an iPhone app, an Apple Watch app with complications, a Mac app with a menu bar item and desktop widgets, a server that streams live train data over gRPC, and an MCP server for Claude and ChatGPT. Almost all of it was written with Claude Code, and the production system runs on Google Cloud Platform, with Cloudflare for DNS.

I'm Joao Martins, CTO of Defaqto. The whole app ecosystem was built in one day, Saturday 7 November 2026. The live data feeds came the next day, because my access to the Trains Data API needed approval first. You can read more about me on LinkedIn.

The timeline

Not even Rome was built in a day

The app was the easy part of the last few days. To submit it to the App Store, Apple needs a privacy policy and terms of use at a public web address, so I built this website, with the policy, the terms and a support page, on top of everything else.

Then there was the paperwork. My Apple Developer subscription had lapsed, and it had to be renewed before I could upload anything. My last app submission was more than five years ago. Their systems were slow then, and they still are. I ended up paying twice, because the subscription status in the store wasn't updated and let me pay again. They have no option to get a refund.

The ecosystem

PartWhat it isBuilt with
ServerLive departures from one API, served over gRPC, Connect, MCP and OAuthTypeScript on Node 22, Connect, MCP SDK
iPhone appTrips, departures with platforms as they're announced, widgetsSwift 6, SwiftUI, WidgetKit, grpc-swift 2
Apple WatchTrips, departures and a complication on the watch faceSwiftUI, WidgetKit timelines, WatchConnectivity
Mac appWindow, menu bar item, Settings, desktop widgets, network-aware locationSwiftUI, MenuBarExtra, App Intents, SystemConfiguration
Claude and ChatGPTAn MCP server with an MCP App board that shows the live departures in the chatMCP streamable HTTP, MCP Apps, a small HTML app
This websitechoochoo.stream, the privacy policy and terms, and this pageNode 22, plain HTML and CSS, no framework
InfrastructureCloud Run, Firestore, Secret Manager, logging and analyticsTerraform, Google Cloud, Cloud Build
Release pipelineTests, container builds, signed iOS and macOS builds to TestFlightGitHub Actions on a self-hosted Mac, App Store Connect API

The architecture

Choo Choo architecture Apps and the Claude and ChatGPT connector call two Cloud Run services and the website. The services read live departures from the Trains Data API, store saved trips in Firestore, sign users in with Google or Apple, and send usage logs to analytics. Releases run from GitHub Actions on a Mac mini runner. Clients Google Cloud Run Data and services iPhone app Swift, SwiftUI, widgets Apple Watch complications, phone link Mac app menu bar, desktop widgets Claude and ChatGPT MCP connector Web browser choochoo.stream gRPC service HTTP/2, streams departures MCP and sign-in HTTP/1.1, OAuth 2.1 Website Node 22, this site Trains Data API live departures Firestore saved trips, in the EU Google and Apple sign-in providers Analytics logs, BigQuery, Looker Studio Secret Manager tokens and keys gRPC streams Connect JSON gRPC MCP HTTPS polls trips polls sign in logs Releases: GitHub Actions on a Mac mini, Cloud Build, Terraform, TestFlight. Cloudflare DNS in front.
How the parts fit together. Arrows show who calls whom.

The data stream, end to end

  1. One poller per query. The server's board hub starts one poller for each station and destination pair that anyone is watching, and every subscriber shares it. The poller stops when the last subscriber leaves.
  2. Only changes are pushed. Each board gets a fingerprint that ignores its timestamp. A subscriber receives a new board only when the content actually changes, so a quiet station sends nothing.
  3. Polling that tightens near the train. Boards are polled every 30 seconds, or every 10 seconds when a train leaves within 15 minutes without a platform. Errors back off exponentially.
  4. A request budget. The upstream API caps requests per minute, hour and day. The server counts its own requests and serves a cached board rather than overspend. Each Cloud Run service has its own share of the budget.
  5. gRPC server streams to the iPhone and Mac. WatchDepartures is a server-streaming RPC over HTTP/2. The app keeps one open per departures screen and reconnects with backoff. Cloud Run ends streams after an hour, and the app reconnects.
  6. Connect JSON to the watch. watchOS apps mostly use URLSession, so the watch makes ordinary HTTP/1.1 calls to the same service with the Connect protocol and fetches departures itself. The phone sends it the trips and a refresh token over WatchConnectivity.
  7. Timelines for the complication. WidgetKit gets one entry for now and one as each train leaves, so the watch face changes on time without the app running. Cancelled trains are skipped, and boards older than three hours are not shown.

Apple platforms

The MCP server and the connector

The MCP server exposes four tools (departures, finding a train's platform, station search, nearest stations). get_departures also returns an MCP App: a small, self-contained page that shows the next trains, refreshes itself every 30 seconds, and follows the light or dark theme of the chat. The server is also its own OAuth 2.1 authorisation server, with PKCE, dynamic client registration and Google or Apple sign-in, so Claude and ChatGPT can connect like any other remote MCP server.

Today you can add it by hand as a custom connector at https://mcp.choochoo.stream/mcp. Publishing it in the Claude and ChatGPT connector directories is the next step.

Google Cloud and Cloudflare

How Claude Code was used

Claude Code wrote most of the code. Joao directed each change, reviewed the diffs, ran the apps in the simulators and on real devices, and decided what to build next. Every feature lived on its own git branch, often in its own worktree, so several changes could be in progress at once. A CLAUDE.md file in the repository is the handoff note: what each part does, what has been verified and what hasn't, and the decisions and why. Claude picks it up at the start of each session, so context carries from one day to the next.

The languages (the ones I don't write by hand)

Across the repository there are about 220 tracked files in these languages and formats:

Language or formatWhere it is used
Swift 6iPhone, Apple Watch, Mac apps and widgets
TypeScriptThe server, the MCP server, OAuth, analytics and the tests
JavaScript (ES modules)Branding and store-screenshot build scripts, the website
Protocol BuffersThe API contract, shared by the server and every app
HCL (Terraform)Google Cloud infrastructure
YAMLGitHub Actions workflows, project configuration
BashDeploy, release and TestFlight scripts
SQLAnalytics views in BigQuery
HTML, CSSThe website, the MCP App board and the sign-in pages
DockerfileContainer images for Cloud Run
JSON, XML property listsAsset catalogues, privacy manifests, app settings
SVG and PNGThe icon, brand artwork and screenshots, all generated by a script
MarkdownDocumentation, the App Store text, the privacy policy

Built with love, one train at a time. Back to the home page.