Skip to main content
Glama

Notion MCP Server

by ghubnerr
import { Client } from "@notionhq/client"; import { LogLevel } from "@notionhq/client"; // Configure default page size for pagination const DEFAULT_PAGE_SIZE = 100; /** * Validates each block in the provided blocks array. * If a block is missing the required content property, logs an error and replaces it with a safe default paragraph block. */ function validateBlocks(blocks: any[]): any[] { return blocks.map((block, index) => { if (!block.type) { console.error( `Block at index ${index} is missing the 'type' field. Replacing with default paragraph block.` ); return { type: "paragraph", object: "block", paragraph: { rich_text: [ { text: { content: "Invalid block removed." }, type: "text" }, ], }, }; } if (typeof block[block.type] === "undefined") { console.error( `Block at index ${index} of type ${block.type} is missing its content property. Replacing with default paragraph block.` ); return { type: "paragraph", object: "block", paragraph: { rich_text: [ { text: { content: "Invalid block removed." }, type: "text" }, ], }, }; } return block; }); } /** * Fetch all databases from the Notion workspace */ export async function fetchDatabases(notion: Client) { try { const response = await notion.search({ filter: { property: "object", value: "database", }, page_size: DEFAULT_PAGE_SIZE, }); return response.results.map((database: any) => ({ id: database.id, title: extractDatabaseTitle(database), url: database.url, created_time: database.created_time, last_edited_time: database.last_edited_time, })); } catch (error) { console.error("Error fetching databases:", error); throw error; } } /** * Fetch pages from a specific database */ export async function fetchPages(notion: Client, databaseId: string) { try { const database = await notion.databases.retrieve({ database_id: databaseId, }); const response = await notion.databases.query({ database_id: databaseId, page_size: DEFAULT_PAGE_SIZE, }); return { database: { id: database.id, title: extractDatabaseTitle(database), }, pages: await Promise.all( response.results.map(async (page: any) => { let title = "Untitled"; const titleProperty = Object.entries(page.properties).find( ([, value]: [string, any]) => value.type === "title" ); if ( titleProperty && titleProperty[1] && (titleProperty[1] as any).title ) { const titleValue = (titleProperty[1] as any).title; title = titleValue.length > 0 ? titleValue[0].plain_text : "Untitled"; } return { id: page.id, title, url: page.url, created_time: page.created_time, last_edited_time: page.last_edited_time, properties: page.properties, }; }) ), }; } catch (error) { console.error(`Error fetching pages from database ${databaseId}:`, error); throw error; } } /** * Fetch content of a specific page */ export async function fetchPageContent(notion: Client, pageId: string) { try { const page = await notion.pages.retrieve({ page_id: pageId, }); const blocks = await fetchAllBlocks(notion, pageId); return { page: { id: page.id, url: (page as any).url, created_time: (page as any).created_time, last_edited_time: (page as any).last_edited_time, properties: (page as any).properties, }, blocks, }; } catch (error) { console.error(`Error fetching content for page ${pageId}:`, error); throw error; } } /** * Recursively fetch all blocks for a page, including nested blocks */ async function fetchAllBlocks( notion: Client, blockId: string, maxDepth = 3, currentDepth = 0 ) { if (currentDepth >= maxDepth) { return []; } try { const blocks: any[] = []; let hasMore = true; let cursor: string | undefined = undefined; while (hasMore) { const response = await notion.blocks.children.list({ block_id: blockId, page_size: DEFAULT_PAGE_SIZE, ...(cursor ? { start_cursor: cursor } : {}), }); blocks.push(...response.results); hasMore = response.has_more; cursor = response.next_cursor || undefined; } const processedBlocks = []; for (const block of blocks) { const processedBlock: any = { id: block.id, type: block.type, has_children: block.has_children, created_time: block.created_time, last_edited_time: block.last_edited_time, ...block[block.type as string], }; if (block.has_children) { processedBlock.children = await fetchAllBlocks( notion, block.id, maxDepth, currentDepth + 1 ); } processedBlocks.push(processedBlock); } return processedBlocks; } catch (error) { console.error(`Error fetching blocks for ${blockId}:`, error); throw error; } } /** * Search for content in Notion */ export async function searchNotion( notion: Client, query: string, filter?: any ) { try { const searchParams: any = { query, page_size: DEFAULT_PAGE_SIZE, }; if (filter?.object) { searchParams.filter = { property: "object", value: filter.object, }; } const response = await notion.search(searchParams); return { results: response.results.map((result: any) => { const common = { id: result.id, object: result.object, url: result.url, created_time: result.created_time, last_edited_time: result.last_edited_time, }; if (result.object === "page") { return { ...common, title: extractPageTitle(result), parent: result.parent, }; } else if (result.object === "database") { return { ...common, title: extractDatabaseTitle(result), properties: result.properties, }; } else { return { ...common, }; } }), next_cursor: response.next_cursor, has_more: response.has_more, }; } catch (error) { console.error(`Error searching Notion with query "${query}":`, error); throw error; } } /** * Create a new page in Notion */ export async function createPage( notion: Client, parent: { databaseId?: string; pageId?: string }, properties: any, content?: any[] ) { try { let parentObject: any = {}; if (parent.databaseId) { parentObject.database_id = parent.databaseId; } else if (parent.pageId) { parentObject.page_id = parent.pageId; } else { throw new Error("Either databaseId or pageId must be provided"); } const newPage = await notion.pages.create({ parent: parentObject, properties, }); if (content && content.length > 0) { const validatedContent = validateBlocks(content); await addBlocksToPage(notion, newPage.id, validatedContent); } return newPage; } catch (error) { console.error("Error creating page:", error); throw error; } } /** * Update an existing page in Notion */ export async function updatePage( notion: Client, pageId: string, properties?: any, content?: any[] ) { try { let updatedPage; if (properties) { updatedPage = await notion.pages.update({ page_id: pageId, properties, }); } if (content && content.length > 0) { const blocks = await notion.blocks.children.list({ block_id: pageId }); for (const block of blocks.results) { await notion.blocks.delete({ block_id: block.id }); } const validatedContent = validateBlocks(content); await addBlocksToPage(notion, pageId, validatedContent); } return updatedPage || { id: pageId }; } catch (error) { console.error(`Error updating page ${pageId}:`, error); throw error; } } /** * Delete a page in Notion (archive it) */ export async function deletePage(notion: Client, pageId: string) { try { await notion.pages.update({ page_id: pageId, archived: true, }); return { success: true }; } catch (error) { console.error(`Error deleting page ${pageId}:`, error); throw error; } } /** * Add blocks to a page */ async function addBlocksToPage(notion: Client, pageId: string, blocks: any[]) { try { const validatedBlocks = validateBlocks(blocks); await notion.blocks.children.append({ block_id: pageId, children: validatedBlocks, }); return { success: true }; } catch (error) { console.error(`Error adding blocks to page ${pageId}:`, error); throw error; } } /** * Fetch recent updates from the workspace */ export async function fetchRecentUpdates(notion: Client, maxItems = 10) { try { const response = await notion.search({ sort: { direction: "descending", timestamp: "last_edited_time", }, page_size: maxItems, }); return response.results.map((item: any) => { const common = { id: item.id, lastEdited: item.last_edited_time, url: item.url, type: item.object, }; if (item.object === "page") { return { ...common, title: extractPageTitle(item), parent: item.parent.type === "database_id" ? "database" : item.parent.type === "page_id" ? "page" : "workspace", }; } else if (item.object === "database") { return { ...common, title: extractDatabaseTitle(item), itemCount: 0, }; } else { return { ...common, title: "Unknown item", }; } }); } catch (error) { console.error("Error fetching recent updates:", error); throw error; } } /** * Helper function to extract a page title */ function extractPageTitle(page: any): string { if (page.properties) { const titleProperty = Object.entries(page.properties).find( ([, value]: [string, any]) => value.type === "title" ); if (titleProperty && titleProperty[1] && (titleProperty[1] as any).title) { const titleValue = (titleProperty[1] as any).title; if (titleValue?.length > 0) { return titleValue[0].plain_text; } } } return "Untitled"; } /** * Helper function to extract a database title */ function extractDatabaseTitle(database: any): string { if ( database.title && Array.isArray(database.title) && database.title.length > 0 ) { return database.title.map((t: any) => t.plain_text).join(""); } return "Untitled Database"; }

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/ghubnerr/Notion-MCP'

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