DEV Community

Neema Adam
Neema Adam

Posted on

Reflective — AI journaling companion built with Notion MCP and Claude

Notion MCP Challenge Submission 🧠

This is a submission for the Notion MCP Challenge


What I Built

Reflective is a Chrome extension + Node.js backend that adds an AI journaling companion to your browser sidebar while you write in Notion.

Most journaling tools are write-only. You pour thoughts in, they sit there. Reflective makes your Notion journal a two-way conversation — without leaving the page.

How it works:

You write in Notion
       ↓
Click "Analyze this entry" in the sidebar
       ↓
Claude reads your entry + your journal history from Notion
       ↓
Opens a conversation grounded in what you actually wrote
       ↓
Click "Mark session complete"
       ↓
Mood score, tags, themes, and AI summary written back to Notion
Enter fullscreen mode Exit fullscreen mode

Key behaviors:

  • Sits in the Chrome side panel alongside Notion — no new tabs
  • Context-aware: on a database view it loads your last 50 entries; on a single entry it reads just that page
  • Temporal awareness: if you're reading an old entry, Claude knows — it frames responses as looking back, not as if it's today
  • One conversation across the whole session — navigating to a new entry appends to the chat with a — now reading: [title] — divider
  • Analysis is always user-initiated. The extension makes zero API calls on navigation.
  • Mood sparkline shows your last 14 days, pulled live from a Notion database

One-click workspace setup. On first launch, it creates a Journal Entries database, Mood Log, Weekly Summaries database, a seeded starter entry, and a dashboard page — all via the Notion API. You paste your integration token, it does the rest in ~10 seconds.


Video Demo


Show Us the Code

Repo: https://github.com/neicore/notion-reflective

Stack:

  • Chrome Extension (Manifest V3, React + TypeScript, Vite, Tailwind)
  • Node.js + Express backend (TypeScript)
  • Anthropic Claude (claude-sonnet-4-5)
  • Notion API (@notionhq/client v2)
  • Server-Sent Events for streaming progress to the extension
  • pnpm workspaces

How I Used Notion MCP

Notion is where users read and write their data — and it's also the entire data model. Every piece of state lives there, accessed via @notionhq/client.

Reading: building context for Claude

When you click "Analyze this entry", the backend fetches in parallel:

const [rawHistory, recentMoods] = await Promise.all([
  fetchJournalEntries(journalDbId, 50, notionToken),  // last 50 entries
  queryMoodHistory(moodLogDbId, 14, notionToken),      // 14-day sparkline
])
Enter fullscreen mode Exit fullscreen mode

Then reads the current page blocks directly:

const [pageObj, blocks] = await Promise.all([
  notion.pages.retrieve({ page_id: pageId }),
  notion.blocks.children.list({ block_id: pageId, page_size: 100 }),
])
Enter fullscreen mode Exit fullscreen mode

The block content becomes the raw text fed to Claude alongside journal history. History is split into "before this date" and "after this date" relative to the entry you're viewing — that's what gives Claude correct temporal framing.

Writing: analysis flows back into Notion

When you mark a session complete:

await notion.pages.update({
  page_id: pageId,
  properties: {
    'Mood Score':       { number: result.moodScore },
    'Mood Tags':        { multi_select: result.moodTags.map(name => ({ name })) },
    'Themes':           { multi_select: result.themes.map(name => ({ name })) },
    'AI Summary':       { rich_text: [{ text: { content: result.aiSummary } }] },
    'Word Count':       { number: wordCount },
    'Session Complete': { checkbox: true },
  },
})
Enter fullscreen mode Exit fullscreen mode

Your Notion database gets richer over time. On future analyses, entries with an AI Summary are used as-is (fast); entries without one get their blocks fetched (slower, shown in the loading progress bar). First load hydrates your journal — subsequent loads are instant.

The loading experience

Hydrating 50 entries takes a while the first time. The /api/init endpoint streams progress via Server-Sent Events:

data: {"type":"journal_count","total":47}
data: {"type":"hydrating","index":1,"total":47,"title":"March reflections"}
data: {"type":"hydrating","index":2,"total":47,"title":"A hard week"}
...
data: {"type":"done","entryContent":"...","openingMessage":"..."}
Enter fullscreen mode Exit fullscreen mode

The extension consumes this with fetch + ReadableStream (more reliable than EventSource in extension contexts), updating the UI: Reading your journal — 3/47 — A hard week.

What Notion unlocks that a plain database wouldn't

Notion's multi-select properties for Mood Tags and Themes mean once entries are analyzed, you can filter, sort, and group your entire journal by mood or theme natively in Notion — no custom query interface needed. The AI populates the properties; Notion's built-in views do the rest.

The mood sparkline pulls from a separate Mood Log database that tracks daily mood independently from journal entries. You can log mood on days you don't write, and the chart reflects it. Two related databases — the kind of structure that's natural in Notion and would need real schema design anywhere else.

That's the part I keep coming back to: the AI populates the properties, Notion's built-in views do the rest. No custom query interface, no separate dashboard to maintain. Your journal just gets smarter over time.


Top comments (0)