Notion MCP Server

by ramidecodes
Verified
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { NotionService } from "./notion.js"; import { z } from "zod"; /** * MCP Server that wraps the Notion SDK */ export class NotionMCPServer { private server: McpServer; private notionService: NotionService; constructor(notionApiKey?: string) { // Initialize the Notion service this.notionService = new NotionService(notionApiKey); // Initialize the MCP server this.server = new McpServer({ name: "notion", version: "1.0.0", description: "MCP server for Notion API", }); // Register all tools this.registerTools(); } /** * Register all tools for the MCP server */ private registerTools(): void { // Search this.registerSearchTool(); // Database operations this.registerDatabaseTools(); // Page operations this.registerPageTools(); // Block operations this.registerBlockTools(); // User operations this.registerUserTools(); // Comment operations this.registerCommentTools(); // Link Preview operations this.registerLinkPreviewTools(); // Helper functions this.registerHelperFunctions(); } /** * Register search-related tools */ private registerSearchTool(): void { this.server.tool( "search", { query: z.string().optional().describe("The search query string"), filter_object_type: z .enum(["page", "database"]) .optional() .describe("Filter by object type"), page_size: z .number() .min(1) .max(100) .optional() .describe("Number of results to return (max 100)"), }, async ({ query, filter_object_type, page_size }) => { const params: any = { page_size }; if (query) params.query = query; if (filter_object_type) { params.filter = { property: "object", value: filter_object_type, }; } try { const results = await this.notionService.search(params); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { console.error("Error in search tool:", error); return { content: [ { type: "text", text: `Error: Failed to search Notion - ${ (error as Error).message }`, }, ], isError: true, }; } } ); } /** * Register database-related tools */ private registerDatabaseTools(): void { // Query database this.server.tool( "query-database", { database_id: z.string().describe("The ID of the database to query"), filter: z .string() .optional() .describe("JSON string of filter criteria"), sorts: z.string().optional().describe("JSON string of sort criteria"), page_size: z .number() .min(1) .max(100) .optional() .describe("Number of results to return (max 100)"), start_cursor: z.string().optional().describe("Pagination cursor"), }, async ({ database_id, filter, sorts, page_size, start_cursor }) => { const params: any = { database_id, page_size, start_cursor }; // Parse filter and sorts from JSON strings to objects // Example filter: "{\"property\":\"Status\",\"select\":{\"equals\":\"Done\"}}" // Example sorts: "[{\"property\":\"Priority\",\"direction\":\"descending\"}]" try { if (filter) params.filter = JSON.parse(filter); if (sorts) params.sorts = JSON.parse(sorts); } catch (parseError) { console.error("Error parsing JSON parameters:", parseError); return { content: [ { type: "text", text: `Error: Failed to parse JSON parameters - ${ (parseError as Error).message }. Make sure filter and sorts are valid JSON strings.`, }, ], isError: true, }; } try { const results = await this.notionService.queryDatabase(params); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { console.error("Error in query-database tool:", error); return { content: [ { type: "text", text: `Error: Failed to query database - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Get database this.server.tool( "get-database", { database_id: z.string().describe("The ID of the database to retrieve"), }, async ({ database_id }) => { try { const database = await this.notionService.retrieveDatabase( database_id ); return { content: [ { type: "text", text: JSON.stringify(database, null, 2), }, ], }; } catch (error) { console.error("Error in get-database tool:", error); return { content: [ { type: "text", text: `Error: Failed to retrieve database - ${ (error as Error).message }`, }, ], isError: true, }; } } ); } /** * Register page-related tools */ private registerPageTools(): void { // Create page this.server.tool( "create-page", { parent_type: z .enum(["database_id", "page_id"]) .describe("Type of parent (database or page)"), parent_id: z.string().describe("ID of the parent database or page"), properties: z.string().describe("JSON string of page properties"), children: z .string() .optional() .describe("JSON string of page content blocks"), }, async ({ parent_type, parent_id, properties, children }) => { const params: any = { parent: { type: parent_type, [parent_type]: parent_id, }, properties: JSON.parse(properties), }; if (children) { params.children = JSON.parse(children); } try { const page = await this.notionService.createPage(params); return { content: [ { type: "text", text: JSON.stringify(page, null, 2), }, ], }; } catch (error) { console.error("Error in create-page tool:", error); return { content: [ { type: "text", text: `Error: Failed to create page - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Get page this.server.tool( "get-page", { page_id: z.string().describe("The ID of the page to retrieve"), }, async ({ page_id }) => { try { const page = await this.notionService.retrievePage(page_id); return { content: [ { type: "text", text: JSON.stringify(page, null, 2), }, ], }; } catch (error) { console.error("Error in get-page tool:", error); return { content: [ { type: "text", text: `Error: Failed to retrieve page - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Update page this.server.tool( "update-page", { page_id: z.string().describe("The ID of the page to update"), properties: z .string() .describe("JSON string of page properties to update"), archived: z .boolean() .optional() .describe("Whether to archive the page"), }, async ({ page_id, properties, archived }) => { const params: any = { page_id, properties: JSON.parse(properties), }; if (archived !== undefined) { params.archived = archived; } try { const page = await this.notionService.updatePage(params); return { content: [ { type: "text", text: JSON.stringify(page, null, 2), }, ], }; } catch (error) { console.error("Error in update-page tool:", error); return { content: [ { type: "text", text: `Error: Failed to update page - ${ (error as Error).message }`, }, ], isError: true, }; } } ); } /** * Register block-related tools */ private registerBlockTools(): void { // Append blocks this.server.tool( "append-blocks", { block_id: z.string().describe("The ID of the block to append to"), children: z.string().describe("JSON string of blocks to append"), }, async ({ block_id, children }) => { try { const result = await this.notionService.appendBlockChildren({ block_id, children: JSON.parse(children), }); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error("Error in append-blocks tool:", error); return { content: [ { type: "text", text: `Error: Failed to append blocks - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Get block this.server.tool( "get-block", { block_id: z.string().describe("The ID of the block to retrieve"), }, async ({ block_id }) => { try { const block = await this.notionService.retrieveBlock(block_id); return { content: [ { type: "text", text: JSON.stringify(block, null, 2), }, ], }; } catch (error) { console.error("Error in get-block tool:", error); return { content: [ { type: "text", text: `Error: Failed to retrieve block - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Get block children this.server.tool( "get-block-children", { block_id: z .string() .describe("The ID of the block to get children from"), page_size: z .number() .min(1) .max(100) .optional() .describe("Number of results to return (max 100)"), start_cursor: z.string().optional().describe("Pagination cursor"), }, async ({ block_id, page_size, start_cursor }) => { try { const blocks = await this.notionService.retrieveBlockChildren({ block_id, page_size, start_cursor, }); return { content: [ { type: "text", text: JSON.stringify(blocks, null, 2), }, ], }; } catch (error) { console.error("Error in get-block-children tool:", error); return { content: [ { type: "text", text: `Error: Failed to retrieve block children - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Update block this.server.tool( "update-block", { block_id: z.string().describe("The ID of the block to update"), properties: z .string() .describe("JSON string of block properties to update"), }, async ({ block_id, properties }) => { try { const block = await this.notionService.updateBlock( block_id, JSON.parse(properties) ); return { content: [ { type: "text", text: JSON.stringify(block, null, 2), }, ], }; } catch (error) { console.error("Error in update-block tool:", error); return { content: [ { type: "text", text: `Error: Failed to update block - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Delete block this.server.tool( "delete-block", { block_id: z.string().describe("The ID of the block to delete"), }, async ({ block_id }) => { try { const result = await this.notionService.deleteBlock(block_id); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error("Error in delete-block tool:", error); return { content: [ { type: "text", text: `Error: Failed to delete block - ${ (error as Error).message }`, }, ], isError: true, }; } } ); } /** * Register user-related tools */ private registerUserTools(): void { // List users this.server.tool("list-users", {}, async () => { try { const results = await this.notionService.listUsers(); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } catch (error) { console.error("Error in list-users tool:", error); return { content: [ { type: "text", text: `Error: Failed to list Notion users - ${ (error as Error).message }`, }, ], isError: true, }; } }); // Get user this.server.tool( "get-user", { user_id: z.string().describe("The ID of the user to retrieve"), }, async ({ user_id }) => { try { const result = await this.notionService.retrieveUser(user_id); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error("Error in get-user tool:", error); return { content: [ { type: "text", text: `Error: Failed to retrieve Notion user - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // Get bot user (me) this.server.tool("get-me", {}, async () => { try { const result = await this.notionService.retrieveMe(); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error("Error in get-me tool:", error); return { content: [ { type: "text", text: `Error: Failed to retrieve bot user - ${ (error as Error).message }`, }, ], isError: true, }; } }); } /** * Register comment-related tools */ private registerCommentTools(): void { // Create comment this.server.tool( "create-comment", { page_id: z.string().describe("The ID of the page to comment on"), text: z.string().describe("The comment text content"), discussion_id: z .string() .optional() .describe("Optional discussion ID for threaded comments"), }, async ({ page_id, text, discussion_id }) => { try { const result = await this.notionService.createComment({ parent: { page_id: page_id, }, rich_text: [ { type: "text", text: { content: text, }, }, ], discussion_id, }); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error("Error in create-comment tool:", error); return { content: [ { type: "text", text: `Error: Failed to create comment - ${ (error as Error).message }`, }, ], isError: true, }; } } ); // List comments this.server.tool( "list-comments", { page_id: z .string() .optional() .describe("The ID of the page to get comments from"), block_id: z .string() .optional() .describe("The ID of the block to get comments from"), start_cursor: z.string().optional().describe("Pagination cursor"), page_size: z .number() .min(1) .max(100) .optional() .describe("Number of results to return (max 100)"), }, async ({ page_id, block_id, start_cursor, page_size }) => { try { if (!page_id && !block_id) { return { content: [ { type: "text", text: "Error: Either page_id or block_id must be provided", }, ], isError: true, }; } const result = await this.notionService.listComments({ page_id, block_id, start_cursor, page_size, }); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error("Error in list-comments tool:", error); return { content: [ { type: "text", text: `Error: Failed to list comments - ${ (error as Error).message }`, }, ], isError: true, }; } } ); } /** * Register Link Preview tools */ private registerLinkPreviewTools(): void { // Create link preview this.server.tool( "create-link-preview", { url: z.string().url().describe("The URL to create a preview for"), page_id: z .string() .optional() .describe("The ID of the page to add the preview to"), }, async ({ url, page_id }) => { try { const result = await this.notionService.createLinkPreview({ url, page_id, }); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error("Error in create-link-preview tool:", error); return { content: [ { type: "text", text: `Error: Failed to create link preview - ${ (error as Error).message }`, }, ], isError: true, }; } } ); } /** * Helper functions for tool usage */ private registerHelperFunctions(): void { // Add helper functions here if needed } /** * Connect the server to a transport */ async connect(transport: any): Promise<void> { await this.server.connect(transport); } } /** * Helper functions for working with the Notion MCP Server */ /** * Helper function to create a properly formatted filter string for the query-database tool * @param filter The filter object * @returns A JSON string representation of the filter * * @example * const filter = createFilterString({ * property: "Status", * select: { * equals: "Done" * } * }); * // Returns: "{\"property\":\"Status\",\"select\":{\"equals\":\"Done\"}}" */ export function createFilterString(filter: any): string { return JSON.stringify(filter); } /** * Helper function to create a properly formatted sorts string for the query-database tool * @param sorts The sorts array * @returns A JSON string representation of the sorts array * * @example * const sorts = createSortsString([ * { * property: "Priority", * direction: "descending" * } * ]); * // Returns: "[{\"property\":\"Priority\",\"direction\":\"descending\"}]" */ export function createSortsString(sorts: any[]): string { return JSON.stringify(sorts); }