Clean, organize, and command your Notion workspace with AI. Powered by the Notion MCP.
π Live Demo β noterunway.pilotronica.com
π DEV.to Article β Read the write-up
- Overview
- Features
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- AI Provider Support
- MCP Integration
- Archive System
- Security
- Scripts
- Author
- Acknowledgements
- License
NoteRunway is a browser-based tool that helps Notion power users clean up, audit, and manage their workspace using a combination of deterministic scans and AI-powered intelligence via the Notion MCP.
It connects to your Notion workspace through OAuth, runs server-side analysis via Next.js API routes, and provides a cyberpunk-themed UI for reviewing findings and approving actions.
| Principle | Description |
|---|---|
| Zero Lock-in | Your data stays in Notion. NoteRunway never stores workspace content. |
| Human-in-the-Loop | No destructive action runs without explicit user approval. |
| Privacy First | AI keys stored only in your browser (BYOK). Sent per-request to NoteRunway APIs and never persisted server-side. |
| Transparency | Every AI decision shows reasoning, similarity scores, and proposed actions. |
NoteRunway includes 7 core tools, split into read-only scanners and AI-powered actions.
Route: /dashboard
Real-time workspace metrics at a glance.
- Total pages β count of all pages in the workspace
- Top-level pages β root pages without a parent
- Recently edited β pages modified in the last 7 days
- Empty pages β pages with zero content blocks
- Link density β percentage of pages referenced by other pages via @mentions
- Quick-access links to all 7 tools
Dependencies:
NotionClient.getWorkspaceStats(),getEmptyPageCount(),getLinkDensity()AI Required: No
Route: /doctor/duplicates
AI-powered semantic duplicate detection with structured output.
How it works:
- Fetches up to 100 non-archive pages with title + content snippet (600 chars max)
- Sends to LLM with a specialized system prompt and Zod schema
- AI returns duplicate groups with similarity scores (clamped between 0.60 and 1.00, shown when confidence β₯ 0.60)
- UI shows grouped pages with the AI's recommended "keep" pick (β)
- User selects which pages to archive β pages go to
NoteRunway Archive/Duplicateswith full audit trail
API:
GET /api/duplicatesβ AI scan (readsx-ai-key,x-ai-modelheaders)POST /api/duplicatesβ Archive confirmed duplicates (Zod-validated body)
Dependencies:
/api/duplicatesroute (GET/POST handlers), duplicate detectionSYSTEM_PROMPT, duplicates Zod schema,NotionClient.getAllPages(),getPageSnippetWithMeta(),moveToArchive()AI Required: Yes β usesgenerateObject()with structured Zod schema at temperature 0
Route: /doctor/garbage
Rule-based detection of workspace clutter across three categories.
| Category | Detection Logic |
|---|---|
| Orphaned | Page has a parent_id but the parent no longer exists in workspace |
| Empty | Page has zero content blocks (blocks.children.list returns empty) |
| Stale | last_edited_time older than threshold (default: 90 days, configurable) |
- Categories are mutually exclusive (orphaned > empty > stale priority)
- Archive pages are always excluded from scans
- Dry-run mode is on by default β review before acting
- Confirmed pages archived to
NoteRunway Archive/Garbage Collection
API:
GET /api/garbage?staleDays=90β Scan workspacePOST /api/garbageβ Archive confirmed garbage (Zod-validated body)
Dependencies:
NotionClient.getGarbagePages(),moveToArchive()AI Required: No
Route: /doctor/deadlinks
Finds broken @mentions pointing to pages that have been deleted or archived.
How it works:
- Scans all non-archive pages for
page_mentionblock types - Cross-references mention targets against the workspace page set
- For missing targets, attempts to resolve the title (returns
nullif hard-deleted) - Displays source page β broken target in a table
API:
GET /api/deadlinksβ Scan and return{ deadLinks[], stats }
Dependencies:
NotionClient.getDeadLinks()AI Required: No
Route: /doctor/sensitive
Two-phase scanner for secrets and PII accidentally stored in Notion.
Phase 1 β Regex Scan (always runs):
Scans all page content (including nested blocks, toggles, callouts) against 13 patterns:
| Pattern | Example Match |
|---|---|
| OpenAI Key | sk-proj-... |
| Anthropic Key | sk-ant-... |
| xAI / Grok Key | xai-... |
| Stripe Secret Key | sk_live_..., sk_test_... |
| Stripe Publishable | pk_live_..., pk_test_... |
| AWS Access Key | AKIA... |
| GitHub Token | ghp_..., gho_..., ghs_... |
| GitHub PAT | github_pat_... |
| PEM Private Key | -----BEGIN PRIVATE KEY----- |
| JWT Token | xxx.xxx.xxx (3-part base64) |
| Database URL | postgres://..., mongodb://... |
| Password in Code | password=..., secret:... |
| Credit Card | Visa, Mastercard, Amex, Discover patterns |
All findings are partially redacted β values are shown as a snippet (first 8 characters + last 4, or first 4 for short matches), never the full value.
Phase 2 β AI Deep Scan (optional):
Sends page text to the LLM to catch natural-language secrets that regex misses (e.g., "the password is hunter2", "login: admin/password123").
API:
GET /api/sensitiveβ Regex-only scanGET /api/sensitive?deepAI=trueβ Regex + AI deep scan (requiresx-ai-keyandx-ai-modelheaders)
Dependencies:
NotionClient.getSensitiveFindings(),getAllPagesWithText(),getModelWithKey()AI Required: Phase 1 no, Phase 2 yes
Route: /graph
Interactive force-directed graph visualization of your workspace structure using React Flow.
- Nodes β Pages, colored by depth level (cyan β purple β green β orange)
- Edges β Parent/child hierarchy (solid lines) + @mention links (dashed lines)
- Orphans β Highlighted in red (no parent, no inbound mentions)
- Interactions β Hover highlights, click to preview, collapse/expand children, mini-map, pan/zoom
API:
GET /api/graphβ Returns{ nodes[], edges[], stats }
Dependencies:
NotionClient.getGraphData(),reactflowAI Required: No
Route: /ask
Natural language interface to your workspace. The most powerful tool in NoteRunway.
How it works:
- User types a free-form instruction
- AI breaks it down into tool calls (search, read, analyze)
- Tool results feed back into the AI context loop (up to 10 steps)
- AI proposes structured actions for user approval
- User reviews proposed changes β confirms or cancels
- Approved actions execute via MCP or NotionClient
Available Tools (server-side):
| Tool | Description |
|---|---|
search_pages |
Search workspace via MCP API-post-search |
get_page |
Fetch page metadata via MCP API-retrieve-a-page |
get_page_content |
Read page blocks via MCP API-get-block-children |
run_analysis |
Run any built-in scanner (dead_links, garbage, workspace_stats, sensitive_data) |
propose_actions |
Propose structured write actions for approval |
Supported Action Types:
| Action | Description |
|---|---|
archive |
Move page to NoteRunway Archive with audit stub |
create |
Create a new page with title + markdown content |
rename |
Update a page's title property |
append |
Add content blocks to the end of a page (non-destructive) |
update |
Replace all content on a page (deletes existing blocks first) |
Streaming: Uses Server-Sent Events (SSE) for real-time streaming of AI responses, tool steps, and proposed actions.
Dependencies:
MCPClient,NotionClient,getModelWithKey(), Vercel AI SDKstreamText()AI Required: Yes β agentic loop with tool calling
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Next.js 16 (App Router) | Full-stack React framework |
| Language | TypeScript 5 | Type safety throughout |
| Styling | Tailwind CSS 4 + shadcn/ui | Utility-first CSS + accessible components |
| AI SDK | Vercel AI SDK 6 | Unified streaming, tool calling, structured output |
| AI Providers | OpenAI, Anthropic, xAI (Grok), Google (Gemini) | Multi-provider BYOK support |
| Notion SDK | @notionhq/client 5 | Official Notion API wrapper |
| MCP | @modelcontextprotocol/sdk + @notionhq/notion-mcp-server | Tool calling via stdio subprocess |
| Graph | React Flow 11 | Interactive graph visualization |
| Validation | Zod 4 | Runtime schema validation |
| Icons | Lucide React | Icon library |
- MCP runs server-side only β stdio transport spawns a child process, which can't run in the browser
- Streaming via SSE β tool steps and AI responses stream in real-time to the frontend
- Lazy MCP connections β MCPClient only connects when the first tool call is needed (saves resources)
- NotionClient for bulk reads β direct SDK for workspace scans (faster than MCP for pagination)
- MCPClient for writes β all mutations go through MCP tools for safety and sandboxing
noterunway/
βββ app/
β βββ page.tsx # Landing page
β βββ layout.tsx # Root layout (dark mode, fonts)
β βββ globals.css # Tailwind + custom styles
β βββ dashboard/
β β βββ page.tsx # Workspace health dashboard
β βββ settings/
β β βββ page.tsx # Notion OAuth + AI provider config
β βββ ask/
β β βββ page.tsx # Semantic Ask chat UI
β βββ graph/
β β βββ page.tsx # Dependency graph visualization
β βββ doctor/
β β βββ duplicates/page.tsx # Duplicate detection
β β βββ garbage/page.tsx # Garbage collector
β β βββ deadlinks/page.tsx # Dead link detector
β β βββ sensitive/page.tsx # Sensitive data finder
β βββ api/
β βββ notion/
β β βββ auth/route.ts # OAuth initiation
β β βββ callback/route.ts # OAuth callback + token exchange
β β βββ status/route.ts # Connection status check
β β βββ disconnect/route.ts # Disconnect workspace
β βββ workspace/
β β βββ stats/route.ts # Workspace metrics
β β βββ empty-pages/route.ts # Empty page count
β β βββ link-density/route.ts # Link density score
β βββ duplicates/route.ts # Duplicate detection (GET scan, POST archive)
β βββ garbage/route.ts # Garbage collector (GET scan, POST archive)
β βββ deadlinks/route.ts # Dead link scan
β βββ sensitive/route.ts # Sensitive data scan + AI deep scan
β βββ graph/route.ts # Workspace graph data
β βββ ask/
β βββ route.ts # Agentic chat (streaming SSE)
β βββ execute/route.ts # Execute approved actions
βββ lib/
β βββ models.ts # AI provider/model registry
β βββ utils.ts # Utility functions (cn)
β βββ hooks/
β β βββ useSettings.ts # Client-side settings hook
β βββ notion/
β β βββ NotionClient.ts # Notion API wrapper (~1280 lines)
β βββ mcp/
β β βββ MCPClient.ts # MCP client wrapper
β βββ ai/
β βββ PromptBuilder.ts # Abstract prompt template
β βββ DuplicateDetectionPromptBuilder.ts
βββ components/
β βββ ui/ # shadcn/ui primitives (button, input, label, select)
β βββ Navbar.tsx # Top navigation bar
β βββ CyberLoader.tsx # Animated loading spinner
β βββ Landing/
β βββ NetworkBackground.tsx # Animated particle canvas
β βββ TerminalDemo.tsx # Interactive terminal animation
β βββ FeatureCard.tsx # Feature showcase cards
β βββ InfoLinks.tsx # Footer links modal
βββ scripts/
β βββ seed-test-workspace.ts # Test data seeder for Notion
βββ public/
β βββ images/ # Static assets
βββ next.config.ts # Security headers
βββ tsconfig.json # TypeScript config
βββ postcss.config.mjs # PostCSS (Tailwind)
βββ eslint.config.mjs # ESLint config
βββ components.json # shadcn/ui config
βββ package.json # Dependencies & scripts
- Node.js >= 20.9.0
- npm (comes with Node.js)
- A Notion workspace you have admin access to
- A Notion OAuth integration (see below)
- An AI API key from any supported provider
git clone https://github.com/your-username/noterunway.git
cd noterunway
npm install- Go to notion.so/my-integrations
- Click "New integration"
- Set the integration type to Public
- Under OAuth Domain & URIs, add your redirect URI:
- Development:
http://localhost:3000/api/notion/callback - Production:
https://your-domain.com/api/notion/callback
- Development:
- Copy the OAuth client ID and OAuth client secret
Create a .env.local file in the project root:
# Notion OAuth (required)
NOTION_OAUTH_CLIENT_ID=your_client_id
NOTION_OAUTH_CLIENT_SECRET=your_client_secret
NOTION_OAUTH_REDIRECT_URI=http://localhost:3000/api/notion/callbackNote: AI API keys are entered in the browser settings page and stored in localStorage. They are never stored in environment variables or on the server.
npm run devOpen http://localhost:3000 in your browser.
- Navigate to Settings (
/settings) - Click "Connect Notion" β you'll be redirected to Notion's OAuth authorization page
- Select the pages you want NoteRunway to access (or select all)
- After authorization, you'll be redirected back to Settings
- Select your AI provider and paste your API key
- Go to Dashboard β you're ready!
NoteRunway supports 4 AI providers with 13 models across two tiers:
| Provider | Smart Models (Best Reasoning) | Fast Models (Bulk Scans) |
|---|---|---|
| OpenAI | GPT-5.4, GPT-4.1 | GPT-5 Mini, GPT-4.1 Mini |
| Anthropic | Claude Opus 4.6, Claude Sonnet 4.6 | Claude Haiku 4.5 |
| xAI (Grok) | Grok 4, Grok 3 | Grok 4.1 Fast, Grok 3 Mini |
| Gemini 2.5 Pro | Gemini 2.5 Flash |
- Keys are stored in browser localStorage only
- Sent to the server per-request via the
x-ai-keyheader - Server creates a provider instance with your key, makes the API call, and discards it
- NoteRunway's server never persists or logs your keys
NoteRunway uses the Notion MCP Server for all workspace write operations.
MCPClient β StdioClientTransport β notion-mcp-server (subprocess)
β
Notion API
- When a tool call is needed,
MCPClient.connect()spawns a subprocess running@notionhq/notion-mcp-server - Communication happens over stdio using JSON-RPC 2.0
- The user's Notion OAuth token is passed as the
NOTION_TOKENenvironment variable - Tool results are parsed from MCP content blocks and returned as structured JSON
- On disconnect, the subprocess is killed to prevent orphaned processes
Tools that modify workspace data require an approved: true flag before execution. Keywords that trigger this gate:
patch, post-page, delete-a-block, move-page, update-a-data-source, create-a-data-source
Read-only tools (search, get) always execute without approval.
β οΈ Deployment Note: MCP stdio transport spawns child processes, which is incompatible with serverless platforms like Vercel. Deploy to Railway, Render, Docker, or a VPS for full MCP functionality.
Every destructive action in NoteRunway creates an audit trail in a dedicated archive structure.
NoteRunway Archive/ β Root page (auto-created)
βββ Duplicates/ β Feature subfolder
β βββ [Audit Stub] Original Title
βββ Garbage Collection/ β Feature subfolder
β βββ [Audit Stub] Stale Page Name
βββ Semantic Ask/ β Feature subfolder
βββ [Audit Stub] Archived via Chat
For each page being archived:
- Discover children β find all nested child pages
- Create audit stub β new page in the feature subfolder containing:
- Callout: "Archived by NoteRunway Β· [date]"
- Reason (if provided)
- Kept version title (for duplicates)
- Divider + original content blocks (faithfully copied)
- Recursively archive children β each child gets its own audit stub, preserving hierarchy
- Soft-archive original β sends the original page to Notion Trash
The following block types are preserved in audit stubs:
paragraph Β· heading_1 Β· heading_2 Β· heading_3 Β· bulleted_list_item Β· numbered_list_item Β· to_do Β· quote Β· callout Β· toggle Β· code Β· divider Β· image Β· bookmark Β· embed
Unsupported types (child_page, synced_block, file) are safely skipped.
- Notion: OAuth 2.0 flow with CSRF state validation. Token stored in an
httpOnly,Securecookie (30-day expiry). - AI Providers: BYOK model β keys stored in browser
localStorage, sent per-request viax-ai-keyheader, never persisted server-side.
Applied globally via next.config.ts:
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevents MIME sniffing |
X-Frame-Options |
DENY |
Prevents clickjacking |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls referrer leakage |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Disables unnecessary browser APIs |
All POST endpoints use Zod schemas for runtime body validation (/api/duplicates, /api/garbage, /api/ask/execute).
Sensitive findings are always displayed as [N chars redacted] β full values are never exposed in the UI or API responses.
| Command | Description |
|---|---|
npm run dev |
Start development server |
npm run build |
Production build |
npm run start |
Start production server |
npm run lint |
Run ESLint |
npm run seed |
Seed Notion workspace with test data |
Giorgi Kobaidze (Pilotronica)
- Notion β for the API and MCP server that makes this project possible
- Notion MCP Server β official Model Context Protocol server for Notion
- Vercel AI SDK β unified streaming, tool calling, and structured output across providers
- React Flow β interactive graph visualization library
- shadcn/ui β accessible, customizable component primitives
- Lucide β beautiful open-source icons
- Built for the DEV Γ Notion Challenge 2025
This project is licensed under the MIT License.
