Skip to main content
Glama

MCP Notion Upload Server

by goonoo
mcp_notion_upload.py10.7 kB
from typing import Any, Optional, Dict import httpx import os from pathlib import Path from mcp.server.fastmcp import FastMCP # Initialize FastMCP server mcp = FastMCP("mcp_notion_upload") @mcp.tool() async def upload_file_to_notion( file_path: str, notion_token: Optional[str] = None, file_name: Optional[str] = None ) -> str: """ Upload a file to Notion using the Notion API. Args: file_path: Path to the file to upload notion_token: Notion API token for authentication (optional if NOTION_API_TOKEN env var is set) file_name: Optional custom filename (defaults to original filename) Returns: The file upload ID from Notion (use upload_and_attach_file_to_page for URL) Raises: Exception: If file doesn't exist, exceeds size limit, or API call fails """ # Get token from parameter or environment variable token = notion_token or os.environ.get('NOTION_API_TOKEN') if not token: raise Exception("Notion API token is required. Pass it as a parameter or set NOTION_API_TOKEN environment variable") # Validate file exists file_path_obj = Path(file_path) if not file_path_obj.exists(): raise Exception(f"File not found: {file_path}") # Check file size (max 20MB) file_size = file_path_obj.stat().st_size max_size = 20 * 1024 * 1024 # 20MB in bytes if file_size > max_size: raise Exception(f"File size ({file_size / 1024 / 1024:.2f}MB) exceeds 20MB limit") # Use custom filename if provided, otherwise use original upload_filename = file_name or file_path_obj.name # Notion API headers headers = { "Authorization": f"Bearer {token}", "Notion-Version": "2022-06-28" } async with httpx.AsyncClient() as client: try: # Step 1: Create file upload object create_upload_response = await client.post( "https://api.notion.com/v1/file_uploads", headers={**headers, "Content-Type": "application/json"}, json={"name": upload_filename} ) create_upload_response.raise_for_status() upload_data = create_upload_response.json() file_upload_id = upload_data.get("id") upload_url = upload_data.get("upload_url") if not file_upload_id or not upload_url: raise Exception("Failed to get upload ID or URL from Notion API") # Step 2: Upload file contents to the upload URL with open(file_path, 'rb') as f: files = {"file": (upload_filename, f)} upload_response = await client.post( upload_url, headers=headers, files=files ) upload_response.raise_for_status() # Step 3: Complete the upload and get the final file object complete_response = await client.get( f"https://api.notion.com/v1/file_uploads/{file_upload_id}", headers=headers ) complete_response.raise_for_status() final_data = complete_response.json() # Return the file_upload_id instead of URL (URL is only available after attachment to page) return file_upload_id except httpx.HTTPStatusError as e: raise Exception(f"Notion API error: {e.response.status_code} - {e.response.text}") except Exception as e: raise Exception(f"Upload failed: {str(e)}") @mcp.tool() async def upload_and_attach_file_to_page( file_path: str, page_id: str, notion_token: Optional[str] = None, file_name: Optional[str] = None, caption: Optional[str] = None ) -> Dict[str, Any]: """ Upload a file to Notion and attach it to a specific page, returning download URL. This function performs the complete workflow: 1. Upload file to Notion 2. Create file block and attach to specified page 3. Extract and return download URL with metadata Args: file_path: Path to the file to upload page_id: Notion page ID to attach the file to (with or without dashes) notion_token: Notion API token for authentication (optional if NOTION_API_TOKEN env var is set) file_name: Optional custom filename (defaults to original filename) caption: Optional caption for the file block Returns: Dictionary containing: - file_upload_id: The uploaded file ID - file_block_id: The created file block ID - download_url: Temporary download URL (expires in 1 hour) - expiry_time: ISO timestamp when download URL expires - filename: Name of the file - content_type: MIME type of the file - file_size: Size of the file in bytes Raises: Exception: If file doesn't exist, exceeds size limit, or API call fails Example: result = await upload_and_attach_file_to_page( "document.pdf", "2785bbc0e5c281f48dfae9a48f53f6a6", notion_token="ntn_xxx" ) download_url = result["download_url"] """ # Get token from parameter or environment variable token = notion_token or os.environ.get('NOTION_API_TOKEN') if not token: raise Exception("Notion API token is required. Pass it as a parameter or set NOTION_API_TOKEN environment variable") # Validate file exists file_path_obj = Path(file_path) if not file_path_obj.exists(): raise Exception(f"File not found: {file_path}") # Check file size (max 20MB) file_size = file_path_obj.stat().st_size max_size = 20 * 1024 * 1024 # 20MB in bytes if file_size > max_size: raise Exception(f"File size ({file_size / 1024 / 1024:.2f}MB) exceeds 20MB limit") # Use custom filename if provided, otherwise use original upload_filename = file_name or file_path_obj.name # Notion API headers headers = { "Authorization": f"Bearer {token}", "Notion-Version": "2022-06-28" } async with httpx.AsyncClient() as client: try: # Step 1: Upload file to Notion # Create file upload object create_upload_response = await client.post( "https://api.notion.com/v1/file_uploads", headers={**headers, "Content-Type": "application/json"}, json={"name": upload_filename} ) create_upload_response.raise_for_status() upload_data = create_upload_response.json() file_upload_id = upload_data.get("id") upload_url = upload_data.get("upload_url") if not file_upload_id or not upload_url: raise Exception("Failed to get upload ID or URL from Notion API") # Upload file contents to the upload URL with open(file_path, 'rb') as f: files = {"file": (upload_filename, f)} upload_response = await client.post( upload_url, headers=headers, files=files ) upload_response.raise_for_status() # Step 2: Create file block and attach to page file_block_payload = { "children": [ { "object": "block", "type": "file", "file": { "type": "file_upload", "file_upload": { "id": file_upload_id }, "caption": [ { "type": "text", "text": { "content": caption or upload_filename } } ] if caption or upload_filename else [] } } ] } # Normalize page ID (remove dashes) normalized_page_id = page_id.replace("-", "") attach_response = await client.patch( f"https://api.notion.com/v1/blocks/{normalized_page_id}/children", headers={**headers, "Content-Type": "application/json"}, json=file_block_payload ) attach_response.raise_for_status() # Step 3: Extract file block information and download URL attach_data = attach_response.json() if not attach_data.get("results"): raise Exception("Failed to create file block") file_block = attach_data["results"][0] file_block_id = file_block.get("id") # Extract file information file_info = file_block.get("file", {}) file_data = file_info.get("file", {}) download_url = file_data.get("url") expiry_time = file_data.get("expiry_time") content_type = file_data.get("content_type", "application/octet-stream") if not download_url: raise Exception("File block created but no download URL available") # Return comprehensive file information return { "file_upload_id": file_upload_id, "file_block_id": file_block_id, "download_url": download_url, "expiry_time": expiry_time, "filename": upload_filename, "content_type": content_type, "file_size": file_size, "status": "success", "message": f"File '{upload_filename}' uploaded and attached successfully" } except httpx.HTTPStatusError as e: error_detail = "" try: error_detail = e.response.json().get("message", e.response.text) except: error_detail = e.response.text raise Exception(f"Notion API error: {e.response.status_code} - {error_detail}") except Exception as e: raise Exception(f"Upload and attach failed: {str(e)}") # Initialize and run the server if __name__ == "__main__": mcp.run(transport='stdio')

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/goonoo/mcp_notion_upload'

If you have feedback or need assistance with the MCP directory API, please join our Discord server