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
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/clientv2) - 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
])
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 }),
])
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 },
},
})
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":"..."}
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)