Skip to main content
Glama
kascoded

Notion MCP Server

by kascoded

Notion MCP Server

A production-grade Model Context Protocol (MCP) server for Notion, providing comprehensive database operations with schema validation and intelligent property handling.

Features

  • šŸ”§ Full CRUD Operations - Create, read, update pages across Notion databases

  • šŸ” Smart Search & Query - Powerful filtering and search capabilities

  • šŸ“Š Schema Management - Automatic schema fetching and validation

  • šŸŽØ Content Formatting - Markdown ↔ Notion blocks conversion

  • ⚔ Async Architecture - High-performance async operations

  • šŸ›”ļø Type Safety - Comprehensive property validation

  • šŸ”„ API 2025-09-03 - Uses latest Notion API with data sources

Related MCP server: Notion MCP Server

Architecture

kas-fastmcp/
ā”œā”€ā”€ main.py                        # Entry point
ā”œā”€ā”€ config.py                      # NotionConfig, loads databases.yaml
ā”œā”€ā”€ notion_server/
│   ā”œā”€ā”€ server.py                  # FastMCP("KasNotionMCP") instance
│   ā”œā”€ā”€ deps.py                    # Shared NotionClient + SchemaManager singletons
│   ā”œā”€ā”€ core/
│   │   ā”œā”€ā”€ client.py              # NotionClient (httpx wrapper, 429 retry, connection pool)
│   │   ā”œā”€ā”€ schema.py              # SchemaManager (10-min TTL, disk cache)
│   │   └── formatters.py          # PropertyFormatter, BlockFormatter (markdown <-> blocks)
│   ā”œā”€ā”€ tools/
│   │   ā”œā”€ā”€ query.py               # notion_query, notion_find_page_by_name, notion_search, notion_list_data_sources, notion_discover_databases
│   │   ā”œā”€ā”€ pages.py               # notion_get_page, notion_get_data_source, notion_create_item, notion_update_item
│   │   ā”œā”€ā”€ content.py             # notion_get_page_content, notion_append_content, notion_replace_content
│   │   └── schema_sync.py         # notion_sync_schemas, notion_validate_config
│   └── utils/
│       └── validators.py          # PropertyValidator
└── databases.yaml                 # Database configuration

Design Principles

  1. Separation of Concerns - Core logic separated from MCP layer

  2. No Circular Dependencies - Clean, linear dependency flow

  3. Immutable Configuration - Config is never mutated at runtime

  4. Schema-First - Validate against actual Notion schemas

  5. Testable - Each layer can be tested independently

Installation

Prerequisites

  • Python 3.10+

  • Notion Integration with API access

  • Notion databases with integration added

Setup

  1. Clone the repository

git clone <your-repo-url>
cd kas-fastmcp
  1. Install dependencies

uv sync
  1. Configure environment

cp .env.example .env
# Edit .env and add your NOTION_TOKEN
  1. Configure databases

cp databases.yaml.example databases.yaml
# Edit databases.yaml and add your database configurations

Getting Your Notion Credentials

  1. Create Integration

  2. Get Data Source IDs

    • Open your database in Notion

    • Click "..." menu → Settings

    • Manage data sources → Copy data source ID

    • Add to databases.yaml

  3. Add Integration to Databases

    • Open each database in Notion

    • Click "..." menu → Connections

    • Add your integration

Configuration

databases.yaml

Define all your Notion databases:

zettelkasten:
  data_source_id: "your-data-source-id-here"
  database_id: "your-database-id-here"
  title_property: "title"
  description: "Personal knowledge management"

habits:
  data_source_id: "your-data-source-id-here"
  database_id: "your-database-id-here"
  title_property: "title"
  description: "Habit tracking"

.env

NOTION_TOKEN=ntn_your_token_here
NOTION_API_VERSION=2025-09-03

Usage

Running the Server

Development:

python main.py

With FastMCP CLI:

fastmcp run main.py

Claude Desktop Integration

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "kas-notion": {
      "command": "python",
      "args": ["/path/to/kas-fastmcp/main.py"]
    }
  }
}

Available Tools (15 total)

Query Tools (query.py)

  • notion_query - Query pages from a database

  • notion_find_page_by_name - Find page by exact title

  • notion_search - Workspace-wide search

  • notion_list_data_sources - List data sources for a database

  • notion_discover_databases - Discover all accessible databases

Page Tools (pages.py)

  • notion_get_page - Get page with properties (fully paginated when include_content=True)

  • notion_get_data_source - Get database schema

  • notion_create_item - Create new page

  • notion_update_item - Update page properties

Content Tools (content.py)

  • notion_get_page_content - Get page content as markdown

  • notion_append_content - Append blocks to page

  • notion_replace_content - Clear all blocks on a page and write new markdown content

Schema Tools (schema_sync.py)

  • notion_sync_schemas - Sync schemas from Notion and optionally update config

  • notion_validate_config - Validate current database configuration

Available Resources

  • notion://databases - Lists all configured databases as JSON

  • notion://databases/{source_name}/schema - Returns the cached schema for a specific database

Testing

Connection Testing

Quick token verification:

python connection_testing/token_verification.py

Full connection test:

python connection_testing/test_connection.py

This will:

  • Validate your configuration

  • Test API connectivity

  • Check database access

  • Fetch schemas

Validation Testing

Test the property validator directly:

python test_validation.py

Development

Adding a New Database

  1. Add to databases.yaml:

my_database:
  data_source_id: "your-id"
  database_id: "your-id"
  title_property: "Name"
  description: "My new database"
  1. No code changes needed! The server automatically loads all databases from the YAML file.

Adding a New Tool

  1. Choose the appropriate module (query.py, pages.py, or content.py)

  2. Use the @mcp.tool decorator

  3. Import and use core modules

  4. Document with clear docstrings

Example:

from notion_server.server import mcp
from notion_server.deps import _client  # shared singleton — do not instantiate a new one

@mcp.tool
async def my_new_tool(param: str) -> dict:
    """
    Tool description.

    Args:
        param: Parameter description

    Returns:
        Result description
    """
    result = await _client.get(f"endpoint/{param}")
    return result

Project Structure Explained

core/ - Pure business logic, no MCP dependencies

  • Testable independently

  • Reusable in other projects

  • No side effects

tools/ - MCP tool definitions

  • Thin wrappers around core modules

  • Handle MCP-specific concerns

  • Minimal business logic

utils/ - Shared utilities

  • Validators

  • Helpers

  • Common functions

Validation System

How It Works

Properties are validated against Notion's schema before API calls:

# Automatic validation
notion_create_item(
    source_name="zettelkasten",
    properties={
        "title": {"title": [{"text": {"content": "My Note"}, "type": "text"}]},
        "nonexistent": {"rich_text": [...]}  # āŒ Validation error!
    }
)

What Gets Validated

āœ… Property exists in schema
āœ… Property type matches
āœ… Select options are valid
āœ… Read-only properties rejected
āœ… Required properties present (strict mode)

Direct Validation

Use the validator directly in your code:

from notion_server.core import SchemaManager, NotionClient
from notion_server.utils import PropertyValidator

client = NotionClient()
schema_manager = SchemaManager(client)
schema = await schema_manager.get_schema("zettelkasten")

validator = PropertyValidator(schema)
is_valid, errors = validator.validate_properties(properties)

Troubleshooting

Common Issues

"NOTION_TOKEN not found"

"Database not found (404)"

  • Check database_id in databases.yaml

  • Ensure integration is added to the database

  • Verify integration has access

"No databases configured"

  • Create databases.yaml from databases.yaml.example

  • Add at least one database configuration

"Property validation failed"

  • Check property names match your schema

  • Ensure values are in correct Notion format

  • Verify select options are valid

Debug Mode

Enable detailed logging by checking server output:

python main.py 2>&1 | tee server.log

API Reference

NotionClient

client = NotionClient(token=None, api_version=None)
await client.get(endpoint)
await client.post(endpoint, payload)
await client.patch(endpoint, payload)

SchemaManager

schema_manager = SchemaManager(client)
schema = await schema_manager.get_schema(source_name)
data_source_id = await schema_manager.get_data_source_id(source_name)
config = schema_manager.get_source_config(source_name)

PropertyFormatter

formatter = PropertyFormatter()
title = formatter.extract_title(properties)
formatted = formatter.format_for_display(properties)

BlockFormatter

formatter = BlockFormatter()
markdown = formatter.to_markdown(blocks)
blocks = formatter.from_markdown(markdown)

Contributing

Code Style

  • Use type hints

  • Write docstrings

  • Keep functions focused

  • Follow separation of concerns

Testing

  • Test core modules independently

  • Test tools through MCP protocol

  • Validate against real Notion databases

License

MIT

Acknowledgments

  • Built with FastMCP

  • Uses Notion API 2025-09-03

  • Developed for AI-powered productivity workflows

See CHANGELOG.md for version history.

Support

For issues, questions, or contributions, please open an issue on GitHub.


Built with ā¤ļø for AI-powered productivity

Related MCP Connectors

Related MCP Servers