Noterunway
AI-Powered Notion Workspace Management Clean, organize, and command your Notion workspace with AI. Powered by the Notion MCP.
Ask AI about Noterunway
Powered by Claude ยท Grounded in docs
I know everything about Noterunway. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
NoteRunway
AI-Powered Notion Workspace Management
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
โถ๏ธ YouTube โ Watch the demo
Table of Contents
- Overview
- Features
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- AI Provider Support
- MCP Integration
- Archive System
- Security
- Scripts
- Author
- Acknowledgements
- License
Overview
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.
Core Principles
| 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. |
Features
NoteRunway includes 7 core tools, split into read-only scanners and AI-powered actions.
1. Workspace Health Dashboard
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
2. Duplicate Detection
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
3. Garbage Collector
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
4. Dead Link Detector
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
5. Sensitive Data Finder
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
6. Dependency Graph
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
7. Semantic Ask (Agentic Chat)
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
Tech Stack
| 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 |
Architecture
Key Design Decisions
- 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
Project Structure
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
Getting Started
Prerequisites
- 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
1. Clone & Install
git clone https://github.com/your-username/noterunway.git
cd noterunway
npm install
2. Create a Notion OAuth Integration
- 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
3. Configure Environment Variables
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/callback
Note: 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.
4. Run the Development Server
npm run dev
Open http://localhost:3000 in your browser.
5. Connect Your Workspace
- 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!
AI Provider Support
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 |
BYOK (Bring Your Own Key)
- 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
MCP Integration
NoteRunway uses the Notion MCP Server for all workspace write operations.
How It Works
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
Destructive Tool Safety
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.
Archive System
Every destructive action in NoteRunway creates an audit trail in a dedicated archive structure.
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
Archive Process
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
Block Copying
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.
Security
Authentication
- 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.
HTTP Security Headers
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 |
Input Validation
All POST endpoints use Zod schemas for runtime body validation (/api/duplicates, /api/garbage, /api/ask/execute).
Sensitive Data Redaction
Sensitive findings are always displayed as [N chars redacted] โ full values are never exposed in the UI or API responses.
Scripts
| 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 |
Author
Giorgi Kobaidze (Pilotronica)
Acknowledgements
- 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
License
This project is licensed under the MIT License.
