Status: Accepted Date: 2025-08-28 Context: Notion Integration Cleanup and CLI Enhancement Decision Maker: Lead Developer (Code Agent) Stakeholders: Chief Architect, Integration Team
Migrate from custom aiohttp-based Notion API implementation to the official notion_client Python library to improve reliability, maintainability, and feature completeness while eliminating custom HTTP request handling and authentication logic.
The existing Notion integration used a custom implementation with several limitations:
Custom HTTP Implementation:
aiohttp request constructionMaintenance Burden:
Feature Limitations:
Files with Custom Implementation:
services/integrations/mcp/notion_adapter.py - Custom aiohttp clientIntegration Points:
cli/commands/notion.pyReplace custom aiohttp-based Notion API implementation with the official notion_client library to leverage official support, improved reliability, and enhanced features.
Library Selection:
# Official Notion Python client
pip install notion-client
Client Initialization:
# BEFORE: Custom aiohttp client
self.session = aiohttp.ClientSession()
# AFTER: Official notion_client
from notion_client import AsyncClient
self.client = AsyncClient(auth=os.getenv("NOTION_API_KEY"))
API Call Migration:
# BEFORE: Custom HTTP requests
async with self.session.get(url, headers=headers) as response:
if response.status == 200:
return await response.json()
# AFTER: Official client methods
result = await self.client.search(query=query, filter=filter_params)
Error Handling:
# BEFORE: Manual status code checking
if response.status != 200:
raise Exception(f"API error: {response.status}")
# AFTER: Official exception handling
try:
result = await self.client.search(...)
except APIResponseError as e:
# Handle specific Notion API errors
Dependency Update:
notion-client to requirementsClient Refactoring:
NotionMCPAdapter to use AsyncClientCLI Enhancement:
create command for page creationpages command with proper page listingTesting Verification:
NotionMCPAdapter Updates:
# services/integrations/mcp/notion_adapter.py
from notion_client import AsyncClient
class NotionMCPAdapter:
def __init__(self):
self.client = AsyncClient(auth=os.getenv("NOTION_API_KEY"))
async def search_notion(self, query: str, filter_type: str = None):
filter_params = {"property": "object", "value": filter_type} if filter_type else {}
return await self.client.search(query=query, filter=filter_params)
async def create_page(self, parent_id: str, properties: dict):
return await self.client.pages.create(
parent={"page_id": parent_id},
properties=properties
)
CLI Command Enhancements:
# cli/commands/notion.py
async def cmd_create(self, title: str, parent_id: Optional[str] = None):
"""Create a new Notion page"""
# Smart parent selection and page creation
result = await self.adapter.create_page(parent_id, properties)
Improved Reliability:
Enhanced Features:
Reduced Maintenance:
Better Integration:
Dependency Risk:
Breaking Changes:
Migration Complexity:
Advanced Features:
Performance Optimization:
Monitoring and Observability:
See ADR-034: Plugin Architecture Implementation for how the Notion integration is now managed as a plugin. The official notion_client library documented in this ADR is now wrapped in the NotionPlugin class, providing dynamic loading, configuration control, and lifecycle management while maintaining the reliability benefits of the official client.
Date: October 15, 2025 Status: ✅ Complete Sprint: A2 (Phase 1) & A3 (Documentation)
Notion released API version 2025-09-03 introducing a fundamental architectural change:
This change enables databases to have multiple data sources but requires using data_source_id instead of database_id for operations like page creation.
Use dynamic data_source_id fetching instead of static configuration
Rationale:
Alternative Rejected:
1. SDK Upgrade:
# requirements.txt
notion-client==2.5.0 # Upgraded from 2.2.1
2. API Version Header:
# services/integrations/mcp/notion_adapter.py
self._notion_client = AsyncClient(
auth=api_key,
client_options=ClientOptions(
notion_version="2025-09-03" # New API version
)
)
3. Dynamic data_source_id Fetching:
async def get_data_source_id(self, database_id: str) -> Optional[str]:
"""
Get primary data_source_id for a database.
Fetches data_sources list from database metadata and returns
the first (primary) data source ID for use in API operations.
"""
db_info = self._notion_client.databases.retrieve(database_id=database_id)
data_sources = db_info.get("data_sources", [])
if not data_sources:
logger.warning("Database has no data sources - may not be migrated yet")
return None
return data_sources[0].get("id")
4. Updated Page Creation:
async def create_database_item(self, database_id: str, properties: Dict, ...):
"""Create page in database using API 2025-09-03 format"""
# Fetch data_source_id dynamically
data_source_id = await self.get_data_source_id(database_id)
if data_source_id:
# New format for API 2025-09-03
parent_param = {
"type": "data_source_id",
"data_source_id": data_source_id
}
else:
# Fallback to legacy format for backward compatibility
parent_param = {"database_id": database_id}
response = self._notion_client.pages.create(
parent=parent_param,
properties=properties,
children=content
)
return response
1. Automatic Handling:
2. Backward Compatibility:
3. Future-Proof:
Real API Testing: October 15, 2025
Test Databases:
25e11704d8bf80deaac2f806390fe7daDuration: 85 minutes (vs 2-3 hour estimate)
Identified Risks:
Deployment Safety:
User Configuration: NO CHANGES REQUIRED ✅
The data_source_id field was intentionally NOT added to NotionConfig schema because:
Updated:
docs/public/user-guides/features/notion-integration.mdSee Also:
What Worked:
What Could Improve:
| Aspect | Status | Notes |
|---|---|---|
| SDK Upgrade | ✅ Complete | 2.2.1 → 2.5.0 |
| API Version | ✅ Complete | 2025-09-03 enabled |
| data_source_id | ✅ Complete | Dynamic fetching |
| Database Ops | ✅ Complete | All operations updated |
| Testing | ✅ Complete | Real API validated |
| Documentation | ✅ Complete | User guide + ADR |
| Deployment | ✅ Ready | Production-ready |
Migration Complete: October 18, 2025