Notion MCP Server

by ramidecodes
Verified
  • docs
# How It Works This document provides a detailed explanation of how the Notion MCP Server works. ## Architecture Overview The Notion MCP Server follows a client-server architecture: ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ MCP Client │ ◄─────► │ MCP Server │ ◄─────► │ Notion API │ │ (Cursor) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ ``` 1. **MCP Client** (e.g., Cursor): Sends requests to the server to perform operations on Notion 2. **MCP Server**: Processes requests, calls the Notion API, and returns responses 3. **Notion API**: The official API provided by Notion ## Communication Protocol The Model Context Protocol (MCP) defines a standardized way for AI models to interact with external tools and resources. The communication happens through a JSON-RPC-like protocol: 1. The client sends a request with a method name and parameters 2. The server processes the request and executes the corresponding tool 3. The server returns a response with the result or an error Example request: ```json { "jsonrpc": "2.0", "id": "1", "method": "tool/search", "params": { "query": "project planning" } } ``` Example response: ```json { "jsonrpc": "2.0", "id": "1", "result": { "content": [ { "type": "text", "text": "{\"results\": [...], \"next_cursor\": null, \"has_more\": false}" } ] } } ``` ## Deployment Options ### 1. Local Subprocess (Recommended for Cursor) In this mode, the server runs as a local subprocess, started on-demand by the client: ``` ┌─────────────────────────────────┐ │ │ │ Client Application (Cursor) │ │ │ │ ┌─────────────────────────┐ │ │ │ │ │ │ │ MCP Server (Subprocess)│ │ │ │ │ │ │ └─────────────────────────┘ │ │ │ └─────────────────────────────────┘ ``` - **Communication**: Standard input/output (stdio) - **Lifecycle**: Started when needed, terminated when done - **Configuration**: Environment variables or .env file - **Advantages**: Simple setup, no hosting required - **Disadvantages**: Limited to local use ### 2. Hosted Service In this mode, the server runs as a persistent service: ``` ┌─────────────┐ ┌─────────────┐ │ │ HTTP/ │ │ │ MCP Client │ ◄─────► │ MCP Server │ │ │ SSE │ (Hosted) │ └─────────────┘ └─────────────┘ ``` - **Communication**: HTTP/SSE (requires additional implementation) - **Lifecycle**: Long-lived process - **Configuration**: Environment variables or configuration file - **Advantages**: Can be used by multiple clients, accessible over the network - **Disadvantages**: More complex setup, requires hosting ## Authentication Flow 1. **Notion API Key**: The server uses a Notion API key to authenticate with the Notion API 2. **Environment Variable**: The key is stored in the `NOTION_API_KEY` environment variable 3. **Integration**: The key is associated with a Notion integration that has access to specific pages/databases 4. **Per-Request Authentication**: Each request to the Notion API includes the API key ## Server Components ### 1. MCP Server The `NotionMCPServer` class is the main component of the server. It: - Initializes the Notion service - Registers tools for interacting with Notion - Connects to a transport (stdio) ### 2. Notion Service The `NotionService` class provides a wrapper around the Notion SDK. It: - Initializes the Notion client with the API key - Provides methods for interacting with the Notion API - Handles error cases and type conversions ### 3. Transport The transport handles the communication between the client and server. Currently, only the stdio transport is implemented: - **StdioServerTransport**: Uses standard input/output for communication ## Tool Implementation Each tool is implemented as a function that: 1. Takes parameters from the client 2. Calls the Notion API 3. Returns the result or an error Example tool implementation: ```typescript this.server.tool( "search", { query: z.string().optional().describe("The search query string"), // ... other parameters }, async ({ query, filter_object_type, page_size }) => { try { const results = await this.notionService.search({ query, filter: filter_object_type ? { property: "object", value: filter_object_type } : undefined, page_size, }); 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, }; } } ); ``` ## Error Handling The server implements comprehensive error handling: 1. **Tool-Level Errors**: Each tool catches errors and returns them in a standardized format 2. **Transport Errors**: The transport handles communication errors 3. **Notion API Errors**: Errors from the Notion API are caught and returned to the client ## Performance Considerations - **Stateless**: The server is stateless, so each request is independent - **Caching**: No caching is implemented, so each request goes to the Notion API - **Rate Limiting**: The Notion API has rate limits that apply to all requests ## Security Considerations - **API Key**: The Notion API key should be kept secure - **Access Control**: The Notion integration should only have access to the necessary pages/databases - **Input Validation**: All input is validated using Zod schemas