AI Notion Integration: Workspace Knowledge Base and Custom Agents

TL;DR — AI Notion integration turns Notion workspaces into AI-powered knowledge hubs. Notion MCP: "Notion MCP is a powerful way to connect your favorite AI apps directly to your Notion workspace. Think of it as a bridge that lets AI assistants like Claude, ChatGPT, and Cursor read from and write to your Notion pages in real-time." Notion 3.3: "Custom Agents are completely autonomous. Just give them a job, set a trigger or schedule, and they will get it done, 24/7. Early testers made over 21,000 Custom Agents, and we have 2,800 agents working around the clock at Notion." TechCrunch: "Notion introduced a new developer platform that extends custom AI agents, connects with external agents, and allows teams to build automated multistep workflows that pull in data from any database. A platform that ties together agents, custom code, and live data starts to look less like a productivity app and more like core infrastructure." Notion Custom Agents: "Agents act only on the pages, databases, and external apps you explicitly grant access to. They never have full workspace access by default, reducing risk." Key components: Notion MCP (Claude, Cursor, ChatGPT), Custom Agents (triggers, schedules, permissions), Workers (custom code sandbox), Database Sync (external APIs to Notion), AI Connectors (Slack, Drive, Jira search). Integrate with Jira/Linear, Nango, company knowledge, and Slack.

TechCrunch describes the evolution: "Notion has quietly become one of the more powerful connective tissues for AI workflows. Most teams already store their documentation, project data, and company knowledge in Notion. By building an orchestration layer — a system that coordinates AI work across multiple tools and data sources — Notion is positioning itself as more than a note-taker with AI features and instead as a hub where people and agents can collaborate across tools and databases."

Notion 3.3 release announces the capability: "Custom Agents are finally here! They are completely autonomous (no manual prompting). Just give them a job, set a trigger or schedule, and they will get it done, 24/7. Your agents can automate task triaging, internal Q&A, daily standups and status reports, and even get you to inbox zero."

AI Notion Integration Architecture

flowchart TD subgraph Notion["Notion Workspace"] Pages["Pages
(docs, wikis, notes)"] Databases["Databases
(tables, boards, calendars)"] Blocks["Blocks
(paragraphs, headings, code, tables)"] Permissions["Permissions
(page-level, database-level)"] end subgraph MCP["Notion MCP"] Bridge["MCP Bridge
(Claude, Cursor, ChatGPT)"] Read["Read Pages
(search, get, query)"] Write["Write Pages
(create, update, append)"] Governance["MCP Governance
(admin-approved apps only)"] end subgraph Agents["Custom Agents"] Triggers["Triggers
(database events, schedules, Slack)"] Context["Context Sources
(Notion, Slack, Mail, Calendar, MCP)"] AutoRun["Autonomous Execution
(24/7, no manual prompting)"] Audit["Audit Log
(all agent changes)"] end subgraph Workers["Developer Platform"] Code["Workers
(custom code sandbox)"] Sync["Database Sync
(external APIs to Notion)"] Webhooks["Webhook Triggers
(external events)"] External["External Agents
(Claude Code, Cursor, Codex)"] end subgraph RAG["RAG Knowledge Base"] Ingest["Ingest Notion Content
(pages, databases)"] Extract["Extract Text
(blocks to text)"] Chunk["Chunk and Embed
(vector embeddings)"] VectorDB["Vector Database
(semantic search)"] Query["Natural Language Query
(with citations)"] end Pages --> Bridge Databases --> Bridge Bridge --> Read Bridge --> Write Governance --> Bridge Triggers --> AutoRun Context --> AutoRun AutoRun --> Pages AutoRun --> Databases AutoRun --> Audit Code --> Sync Sync --> Databases Webhooks --> Code External --> Bridge Pages --> Ingest Databases --> Ingest Ingest --> Extract Extract --> Chunk Chunk --> VectorDB VectorDB --> Query

Notion Integration Methods

Method What It Does Auth Best For
Notion MCP AI agents read/write Notion pages OAuth or integration token Claude, Cursor, ChatGPT
Custom Agents Autonomous agents with triggers Built into Notion Task triage, Q&A, reports
Workers Custom code in sandbox Developer Platform Data sync, webhooks
Database Sync Pull external API data into Notion Workers + external API Salesforce, Zendesk, Postgres
AI Connectors Search across connected apps Workspace admin Slack, Google Drive, Jira
Notion API Direct REST API access Internal integration token Custom RAG pipelines
External Agents Chat with external AI agents MCP connection Claude Code, Cursor, Codex

Custom Agent Triggers

Trigger Type What It Does Example
Database event Agent runs when page created/updated New ticket in triage database
Schedule Agent runs on recurring schedule Daily standup summary at 9 AM
Slack message Agent runs on new Slack message Answer questions in #support
Slack reaction Agent runs on emoji reaction Summarize thread when 📌 reacted
Manual Agent runs when manually triggered On-demand report generation

Implementation

import httpx
import json
from datetime import datetime

class NotionAIIntegration:
    """AI integration for Notion workspace — MCP, Custom Agents, RAG."""

    def __init__(self, notion_token: str, vector_db, embedding_model, audit):
        self.client = httpx.AsyncClient(
            base_url="https://api.notion.com/v1",
            headers={
                "Authorization": f"Bearer {notion_token}",
                "Notion-Version": "2022-06-28",
            },
        )
        self.vector_db = vector_db
        self.embeddings = embedding_model
        self.audit = audit

    async def search_pages(self, query: str, page_size: int = 10) -> list:
        """Search Notion pages by query."""
        response = await self.client.post("/search", json={
            "query": query,
            "page_size": page_size,
        })
        return response.json().get("results", [])

    async def get_page_content(self, page_id: str) -> dict:
        """Get full page content including blocks."""
        # Get page metadata
        page = await self.client.get(f"/pages/{page_id}")
        page_data = page.json()

        # Get all blocks (paginated)
        blocks = []
        start_cursor = None
        while True:
            params = {}
            if start_cursor:
                params["start_cursor"] = start_cursor

            response = await self.client.get(
                f"/blocks/{page_id}/children", params=params)
            data = response.json()
            blocks.extend(data.get("results", []))

            if not data.get("has_more"):
                break
            start_cursor = data.get("next_cursor")

        # Convert blocks to text
        text = self._blocks_to_text(blocks)

        return {
            "page_id": page_id,
            "title": self._extract_title(page_data),
            "text": text,
            "last_edited_time": page_data.get("last_edited_time"),
            "url": page_data.get("url", ""),
        }

    async def ingest_workspace(self, tenant_id: str,
                               root_page_id: str = None) -> dict:
        """Ingest Notion pages into RAG knowledge base."""
        # 1. Search for all accessible pages
        if root_page_id:
            pages = await self._get_child_pages(root_page_id)
        else:
            pages = await self.search_pages("", page_size=100)

        # 2. Process each page
        chunks_created = 0
        for page in pages:
            if page["object"] != "page":
                continue

            content = await self.get_page_content(page["id"])

            # 3. Chunk the content
            chunks = self._chunk_content(content["text"])

            # 4. Generate embeddings and store
            for i, chunk in enumerate(chunks):
                embedding = await self.embeddings.embed(chunk)

                await self.vector_db.insert({
                    "tenant_id": tenant_id,
                    "source": "notion",
                    "page_id": page["id"],
                    "page_title": content["title"],
                    "page_url": content["url"],
                    "chunk_index": i,
                    "chunk_text": chunk,
                    "embedding": embedding,
                    "last_edited_time": content["last_edited_time"],
                    "ingested_at": datetime.utcnow(),
                })
                chunks_created += 1

        # 5. Audit log
        await self.audit.log(
            tenant_id=tenant_id,
            action="notion_workspace_ingested",
            entity_type="notion_workspace",
            after={"pages_processed": len(pages),
                   "chunks_created": chunks_created},
        )

        return {
            "pages_processed": len(pages),
            "chunks_created": chunks_created,
        }

    async def query_knowledge_base(self, tenant_id: str,
                                   query: str, top_k: int = 10) -> dict:
        """Query the Notion knowledge base with natural language."""
        embedding = await self.embeddings.embed(query)

        results = await self.vector_db.search(
            embedding=embedding,
            filter={"tenant_id": tenant_id, "source": "notion"},
            top_k=top_k,
        )

        return {
            "query": query,
            "results": [
                {
                    "page_title": r["page_title"],
                    "page_url": r["page_url"],
                    "chunk_text": r["chunk_text"],
                    "score": r["score"],
                }
                for r in results
            ],
        }

    async def create_page(self, parent_id: str, title: str,
                          content: str) -> dict:
        """Create a new Notion page (AI agent writes to workspace)."""
        blocks = self._text_to_blocks(content)

        response = await self.client.post("/pages", json={
            "parent": {"page_id": parent_id},
            "properties": {
                "title": {
                    "title": [{"text": {"content": title}}]
                }
            },
            "children": blocks,
        })

        return response.json()

    async def append_to_page(self, page_id: str, content: str) -> dict:
        """Append content to an existing Notion page."""
        blocks = self._text_to_blocks(content)

        response = await self.client.patch(
            f"/blocks/{page_id}/children", json={"children": blocks})

        return response.json()

    def _blocks_to_text(self, blocks: list) -> str:
        """Convert Notion blocks to plain text."""
        text_parts = []
        for block in blocks:
            block_type = block.get("type", "")
            if block_type == "paragraph":
                rich_text = block.get("paragraph", {}).get("rich_text", [])
                text = "".join(r.get("plain_text", "") for r in rich_text)
                text_parts.append(text)
            elif block_type.startswith("heading_"):
                rich_text = block.get(block_type, {}).get("rich_text", [])
                text = "".join(r.get("plain_text", "") for r in rich_text)
                text_parts.append(f"\n## {text}\n")
            elif block_type == "bulleted_list_item":
                rich_text = block.get("bulleted_list_item", {}).get("rich_text", [])
                text = "".join(r.get("plain_text", "") for r in rich_text)
                text_parts.append(f"- {text}")
            elif block_type == "numbered_list_item":
                rich_text = block.get("numbered_list_item", {}).get("rich_text", [])
                text = "".join(r.get("plain_text", "") for r in rich_text)
                text_parts.append(f"1. {text}")
            elif block_type == "code":
                rich_text = block.get("code", {}).get("rich_text", [])
                text = "".join(r.get("plain_text", "") for r in rich_text)
                lang = block.get("code", {}).get("language", "")
                text_parts.append(f"```{lang}\n{text}\n```")
            elif block_type == "toggle":
                rich_text = block.get("toggle", {}).get("rich_text", [])
                text = "".join(r.get("plain_text", "") for r in rich_text)
                text_parts.append(f"[Toggle] {text}")

        return "\n\n".join(text_parts)

    def _text_to_blocks(self, text: str) -> list:
        """Convert plain text to Notion blocks (simplified)."""
        blocks = []
        for line in text.split("\n"):
            if line.strip():
                blocks.append({
                    "object": "block",
                    "type": "paragraph",
                    "paragraph": {
                        "rich_text": [{"text": {"content": line}}]
                    },
                })
        return blocks[:100]  # Notion API limit: 100 blocks per request

    def _chunk_content(self, text: str, max_chunk: int = 1000) -> list:
        """Split text into chunks for embedding."""
        paragraphs = text.split("\n\n")
        chunks = []
        current = ""
        for para in paragraphs:
            if len(current) + len(para) > max_chunk:
                if current:
                    chunks.append(current)
                current = para
            else:
                current = current + "\n\n" + para if current else para
        if current:
            chunks.append(current)
        return chunks

    def _extract_title(self, page_data: dict) -> str:
        """Extract page title from Notion page data."""
        props = page_data.get("properties", {})
        if "title" in props and "title" in props["title"]:
            return "".join(
                r.get("plain_text", "")
                for r in props["title"]["title"]
            )
        return "Untitled"

    async def _get_child_pages(self, page_id: str) -> list:
        """Get all child pages of a parent page."""
        blocks = await self.client.get(f"/blocks/{page_id}/children")
        children = blocks.json().get("results", [])
        return [c for c in children if c.get("type") == "child_page"]

AI Notion Integration Checklist

  • [ ] Create Notion internal integration at notion.so/my-integrations
  • [ ] Generate integration token and store encrypted at rest
  • [ ] Share specific pages and databases with the integration
  • [ ] Configure Notion MCP for AI agent connections (Claude, Cursor, ChatGPT)
  • [ ] Set up MCP governance — admin-approved AI apps only (Enterprise plan)
  • [ ] Restrict AI apps to approved list in Settings > Notion AI
  • [ ] Create Custom Agents for autonomous workflows (Business/Enterprise plan)
  • [ ] Configure Custom Agent triggers (database events, schedules, Slack)
  • [ ] Set Custom Agent context sources (Notion pages, databases, Slack, MCP)
  • [ ] Configure Custom Agent permissions — only explicitly granted pages
  • [ ] Enable Custom Agent audit logging for compliance
  • [ ] Set up Notion Workers for custom code (Developer Platform)
  • [ ] Deploy Workers to secure sandbox for data sync and webhooks
  • [ ] Configure database sync from external APIs (Salesforce, Zendesk, Postgres)
  • [ ] Set up webhook triggers for external events
  • [ ] Connect external AI agents (Claude Code, Cursor, Codex, Decagon)
  • [ ] Configure AI Connectors for cross-app search (Slack, Google Drive, Jira)
  • [ ] Implement Notion API client for RAG ingestion
  • [ ] Search pages via Notion API (/v1/search)
  • [ ] Get page content via Notion API (/v1/pages/{id}, /v1/blocks/{id}/children)
  • [ ] Convert Notion blocks to text (paragraphs, headings, lists, code, tables)
  • [ ] Chunk content for embedding (paragraph-level, 1000 char max)
  • [ ] Generate vector embeddings for each chunk
  • [ ] Store in vector database with metadata (page_id, title, url, edited_time)
  • [ ] Implement natural language query with semantic search
  • [ ] Return results with source citations (page title, URL)
  • [ ] Implement incremental sync (poll for last_edited_time changes)
  • [ ] Handle Notion API pagination (start_cursor, has_more, next_cursor)
  • [ ] Handle Notion API rate limits (3 requests per second)
  • [ ] Implement page creation for AI agent write-back
  • [ ] Implement page append for adding content to existing pages
  • [ ] Respect Notion permissions — users can only access their pages
  • [ ] Integrate with Jira/Linear for project sync
  • [ ] Use Nango for Notion OAuth management
  • [ ] Connect to company knowledge RAG pipeline
  • [ ] Notify Slack from Notion Custom Agents
  • [ ] Log all Notion operations in audit logs
  • [ ] Apply RBAC to Notion access
  • [ ] Apply multi-tenant workspace isolation
  • [ ] Apply zero-trust architecture to Notion data
  • [ ] Apply OWASP LLM Top 10 security controls
  • [ ] Set up platform monitoring for Notion
  • [ ] Monitor: pages indexed, sync latency, query latency, API errors
  • [ ] Consider semantic answer caching for queries
  • [ ] Consider AI incident response for sync failures
  • [ ] Test: MCP connection with Claude, Cursor, ChatGPT
  • [ ] Test: Custom Agent triggers and autonomous execution
  • [ ] Test: RAG ingestion and query accuracy
  • [ ] Test: page creation and append by AI agents
  • [ ] Test: permission enforcement (user can't access unauthorized pages)
  • [ ] Document Notion integration capabilities and setup
  • [ ] Choose: Custom Agents for in-Notion automation, API for external RAG

FAQ

What is AI Notion integration?

AI Notion integration connects AI agents to Notion workspaces via MCP (Model Context Protocol) and Custom Agents, enabling reading, writing, and automating workflows within Notion. Notion MCP docs: "Notion MCP is a powerful way to connect your favorite AI apps directly to your Notion workspace. Think of it as a bridge that lets AI assistants like Claude, ChatGPT, and Cursor read from and write to your Notion pages in real-time." Notion 3.3: "Custom Agents are completely autonomous. Just give them a job, set a trigger or schedule, and they will get it done, 24/7." Key capabilities: (1) MCP connection — AI agents read/write Notion pages. (2) Custom Agents — autonomous agents with triggers and schedules. (3) Workers — custom code deployment in secure sandbox. (4) Database sync — pull data from external APIs into Notion. (5) AI Connectors — search across Slack, Google Drive, Jira from Notion.

How does Notion MCP work with AI agents?

Notion MCP connects external AI apps to Notion workspaces using the Model Context Protocol. Notion: "Unlike traditional integrations, MCP is designed specifically for AI agents. It is fast, context-aware, and works seamlessly with how you already use Notion. No complex setup required — just connect once and your AI apps can instantly access your workspace." Supported AI apps: (1) Anthropic Claude — Claude Desktop or Claude.ai. (2) Cursor — AI-powered code editor with Notion integration. (3) ChatGPT — OpenAI's AI assistant. (4) Any MCP-compatible tool. Enterprise governance: admins can approve specific AI apps and block all others. MCP does not bypass Notion permissions — agents can only access pages the user has access to. Notion: "Only admin-approved tools can connect, and MCP does not bypass Notion permissions."

What are Notion Custom Agents?

Notion Custom Agents are autonomous AI agents built directly into Notion that run on triggers and schedules. Notion 3.3: "Custom Agents are completely autonomous (no manual prompting). Just give them a job, set a trigger or schedule, and they will get it done, 24/7." Key features: (1) Triggers — database events (page created/updated), schedules (daily, weekly), Slack messages. (2) Context sources — Notion pages, databases, Slack, Mail, Calendar, MCP integrations (Linear, Figma, HubSpot). (3) Permissions — agents act only on explicitly granted pages and databases, never full workspace access. (4) Audit logging — all agent configuration changes recorded in Notion audit log. (5) Team sharing — create and share agents with team members. (6) Model selection — choose which AI model the agent uses. Notion: "Agents act only on the pages, databases, and external apps you explicitly grant access to. They never have full workspace access by default, reducing risk." Early testers made over 21,000 Custom Agents; Notion runs 2,800 agents internally.

What are Notion Workers and the Developer Platform?

Notion Workers and the Developer Platform extend Notion into a programmable platform for AI workflows. TechCrunch: "Notion introduced a new developer platform that extends the capabilities of its custom AI agents, connects with external agents, and allows teams to build automated multistep workflows that can pull in data from any database." Workers: (1) Cloud-based environment for running custom code. (2) Deploy logic to a secure sandbox (isolated environment). (3) Sync data from external APIs into Notion databases. (4) Trigger work with webhooks. (5) No external infrastructure needed. Database sync: pull data from Salesforce, Zendesk, Postgres, and any API into Notion databases, kept current automatically. External agent integration: chat with external AI agents (Claude Code, Cursor, Codex, Decagon), assign work, and track progress as if they were Notion's own agents. TechCrunch: "A platform that ties together agents, custom code, and live data in one place starts to look less like a productivity app and more like core infrastructure."

How do you use Notion as a RAG knowledge base?

Use Notion as a RAG knowledge base by ingesting Notion pages and databases into a vector database for semantic search. n8n: "The Notion Database Assistant uses an AI Agent built with Retrieval-Augmented Generation (RAG) to query this Knowledge Base style Notion database." Implementation: (1) Connect to Notion API — use internal integration token or OAuth. (2) List pages and databases — use Notion API to enumerate accessible content. (3) Extract content — convert Notion blocks (paragraphs, headings, lists, code, tables) to text. (4) Chunk and embed — split into semantic chunks, generate vector embeddings. (5) Store in vector DB — with metadata (page_id, title, database_id, last_edited_time, permissions). (6) Query — natural language search returns relevant Notion content with source citations. (7) Incremental sync — poll Notion API for updated pages (last_edited_time changes). Notion AI Connectors: "New content may take up to 3 hours to be indexed by Notion AI before it appears in search results." Notion honors existing permissions — users can only retrieve content they have access to.


Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →