What it does

Hanamaru turns school newsletters (photos, PDFs, or copied text) into a fully-populated family Google Calendar. A parent drops a newsletter into Slack or the web UI; the agent reads it with Gemini, extracts the events, assigns them to the right family members, checks for household conflicts, and writes everything to Google Calendar — no manual data entry.

Inspiration

Every week families re-type dates and times from paper or PDF school newsletters into a shared calendar. In my own family this was easy to forget or get wrong, leading to "Wait, what time is the event?" the night before. I wanted a small agent that quietly does this boring work and keeps the family calendar magically up to date.

How I built it

Hanamaru uses Vertex AI Gemini 2.5 Flash as a multimodal extractor: one generateContent call with a JSON Schema response turns a newsletter into structured events (title, start/end, location, family member).

A TypeScript schedule agent then converts datetimes to epoch-millisecond ranges and talks to MongoDB Atlas via the official mongodb-mcp-server over stdio. It inserts events with pre-computed startMs/endMs, runs a deterministic half-open overlap query (startMs < newEnd && endMs > newStart) to detect conflicts, and posts the result back to the Slack thread.

The web service is a Node.js 22 + Hono app on Cloud Run. Firestore holds hot-path state (idempotency keys, pending confirmations) so Slack retries are safe; Secret Manager stores credentials; Terraform manages the infrastructure. A feature flag, ENABLE_MONGO_MCP, gates the MongoDB/MCP path so the production calendar flow keeps working even with it off.

Challenges and what I learned

The main challenge was deciding what to give the LLM versus keep in code. Asking Gemini to build queries or compute epoch ranges made conflict detection hard to debug, so I moved all persistence and conflict logic into TypeScript and used MCP only as a clean tool interface to MongoDB.

Making the demo safe was another: the /api/extract endpoint runs the full Gemini + MCP pipeline as a "dry run," returning the tool-call trace as JSON without ever touching Google Calendar or Firestore. I also added Slack idempotency, backoff retries around Gemini, and graceful degradation so the agent never throws — on any failure it logs a warning, skips conflict detection, and still writes the events.

Overall I learned how well Gemini's multimodal extraction pairs with a deterministic MCP-backed data layer: the model handles messy documents while the code owns precise time arithmetic, conflict logic, and database queries.

Built With

Share this project:

Updates

Submission history