API-get-one-pager
Retrieve a complete Notion page with all blocks, databases, and related content in a single API call. Specify recursion depth, include comments, properties, and linked databases for comprehensive access.
Instructions
Recursively retrieve a full Notion page with all its blocks, databases, and related content
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| batchSize | No | Batch size for parallel processing (default: 10) | |
| includeComments | No | Whether to include comments (default: true) | |
| includeDatabases | No | Whether to include linked databases (default: true) | |
| includeProperties | No | Whether to include detailed page properties (default: true) | |
| maxDepth | No | Maximum recursion depth (default: 5) | |
| maxParallelRequests | No | Maximum number of parallel requests (default: 15) | |
| page_id | Yes | Identifier for a Notion page | |
| runInBackground | No | Process request in background without timeout (default: true) | |
| timeoutMs | No | Timeout in milliseconds (default: 300000) |
Implementation Reference
- src/openapi-mcp-server/mcp/proxy.ts:129-177 (registration)Registers the 'API-get-one-pager' tool in the ListToolsRequestSchema handler, including full input schema definition.const onePagerTool = { name: 'API-get-one-pager', description: 'Recursively retrieve a full Notion page with all its blocks, databases, and related content', inputSchema: { type: 'object', properties: { page_id: { type: 'string', description: 'Identifier for a Notion page', }, maxDepth: { type: 'integer', description: 'Maximum recursion depth (default: 5)', }, includeDatabases: { type: 'boolean', description: 'Whether to include linked databases (default: true)', }, includeComments: { type: 'boolean', description: 'Whether to include comments (default: true)', }, includeProperties: { type: 'boolean', description: 'Whether to include detailed page properties (default: true)', }, maxParallelRequests: { type: 'integer', description: 'Maximum number of parallel requests (default: 15)', }, batchSize: { type: 'integer', description: 'Batch size for parallel processing (default: 10)', }, timeoutMs: { type: 'integer', description: 'Timeout in milliseconds (default: 300000)', }, runInBackground: { type: 'boolean', description: 'Process request in background without timeout (default: true)', } }, required: ['page_id'], } as Tool['inputSchema'], }; tools.push(onePagerTool); console.log(`- ${onePagerTool.name}: ${onePagerTool.description}`);
- Dispatch handler in CallToolRequestSchema that routes 'API-get-one-pager' calls to the specific implementation method.if (name === 'API-get-one-pager') { return await this.handleOnePagerRequest(params); }
- Core handler function that orchestrates the one-pager logic: parses parameters, handles foreground/background modes, recursive retrieval, and response formatting.private async handleOnePagerRequest(params: any) { if (params.runInBackground !== false) { console.log('Starting One Pager request processing:', params.page_id); } const options: RecursiveExplorationOptions = { maxDepth: params.maxDepth || 5, includeDatabases: params.includeDatabases !== false, includeComments: params.includeComments !== false, includeProperties: params.includeProperties !== false, maxParallelRequests: params.maxParallelRequests || 15, skipCache: params.skipCache || false, batchSize: params.batchSize || 10, timeoutMs: params.timeoutMs || 300000, // Increased timeout to 5 minutes (300000ms) runInBackground: params.runInBackground !== false, }; if (options.runInBackground) { console.log('Exploration options:', JSON.stringify(options, null, 2)); } try { const startTime = Date.now(); // Check if we should run in background mode if (options.runInBackground) { // Return immediately with a background processing message // The actual processing will continue in the background this.runBackgroundProcessing(params.page_id, options); return { content: [ { type: 'text', text: JSON.stringify({ status: 'processing', message: `Request processing for page ${params.page_id} started in background`, page_id: params.page_id, request_time: new Date().toISOString(), options: { maxDepth: options.maxDepth, includeDatabases: options.includeDatabases, includeComments: options.includeComments, includeProperties: options.includeProperties, timeoutMs: options.timeoutMs } }), }, ], }; } // Foreground processing (standard behavior) const pageData = await this.retrievePageRecursively(params.page_id, options); const duration = Date.now() - startTime; if (options.runInBackground) { console.log(`One Pager completed in ${duration}ms for page ${params.page_id}`); } return { content: [ { type: 'text', text: JSON.stringify({ ...pageData, _meta: { processingTimeMs: duration, retrievedAt: new Date().toISOString(), options: { maxDepth: options.maxDepth, includeDatabases: options.includeDatabases, includeComments: options.includeComments, includeProperties: options.includeProperties } } }), }, ], }; } catch (error) { if (options.runInBackground) { console.error('Error in One Pager request:', error); } const errorResponse = { status: 'error', message: error instanceof Error ? error.message : String(error), code: error instanceof HttpClientError ? error.status : 500, details: error instanceof HttpClientError ? error.data : undefined, timestamp: new Date().toISOString() }; return { content: [ { type: 'text', text: JSON.stringify(errorResponse), }, ], }; } }
- Key helper function for recursively retrieving full page structure including blocks, properties, comments, and databases with depth limiting, caching, and parallelism.private async retrievePageRecursively(pageId: string, options: RecursiveExplorationOptions, currentDepth: number = 0): Promise<any> { if (options.runInBackground) { console.log(`Recursive page exploration: ${pageId}, depth: ${currentDepth}/${options.maxDepth || 5}`); } const timeoutPromise = new Promise<never>((_, reject) => { if (options.timeoutMs && options.timeoutMs > 0) { setTimeout(() => reject(new Error(`Operation timed out after ${options.timeoutMs}ms`)), options.timeoutMs); } }); try { // Check maximum depth if (currentDepth >= (options.maxDepth || 5)) { if (options.runInBackground) { console.log(`Maximum depth reached: ${currentDepth}/${options.maxDepth || 5}`); } return { id: pageId, note: "Maximum recursion depth reached" }; } // 1. Get basic page info (check cache) let pageData: any; if (!options.skipCache && this.pageCache.has(pageId)) { pageData = this.pageCache.get(pageId); if (options.runInBackground) { console.log(`Page cache hit: ${pageId}`); } } else { // Retrieve page info via API call const operation = this.findOperation('API-retrieve-a-page'); if (!operation) { throw new Error('API-retrieve-a-page method not found.'); } if (options.runInBackground) { console.log(`Notion API call: ${operation.method.toUpperCase()} ${operation.path} (pageId: ${pageId})`); } // Only race with timeout if timeoutMs is set let response; if (options.timeoutMs && options.timeoutMs > 0) { response = await Promise.race([ this.httpClient.executeOperation(operation, { page_id: pageId }), timeoutPromise ]) as any; } else { response = await this.httpClient.executeOperation(operation, { page_id: pageId }); } if (response.status !== 200) { if (options.runInBackground) { console.error('Error retrieving page information:', response.data); } return { id: pageId, error: "Failed to retrieve page", status: response.status, details: response.data }; } pageData = response.data; // Only cache successful responses this.pageCache.set(pageId, pageData); } // Collection of tasks to be executed in parallel for improved efficiency const parallelTasks: Promise<any>[] = []; // 2. Fetch block content (register async task) const blocksPromise = this.retrieveBlocksRecursively(pageId, options, currentDepth + 1); parallelTasks.push(blocksPromise); // 3. Fetch property details (if option enabled) let propertiesPromise: Promise<any> = Promise.resolve(null); if (options.includeProperties && pageData.properties) { propertiesPromise = this.enrichPageProperties(pageId, pageData.properties, options); parallelTasks.push(propertiesPromise); } // 4. Fetch comments (if option enabled) let commentsPromise: Promise<any> = Promise.resolve(null); if (options.includeComments) { commentsPromise = this.retrieveComments(pageId, options); parallelTasks.push(commentsPromise); } // Execute all tasks in parallel if (options.timeoutMs && options.timeoutMs > 0) { await Promise.race([Promise.all(parallelTasks), timeoutPromise]); } else { await Promise.all(parallelTasks); } // Integrate results into the main page data const enrichedPageData = { ...pageData }; // Add block content const blocksData = await blocksPromise; enrichedPageData.content = blocksData; // Add property details (if option enabled) if (options.includeProperties && pageData.properties) { const enrichedProperties = await propertiesPromise; if (enrichedProperties) { enrichedPageData.detailed_properties = enrichedProperties; } } // Add comments (if option enabled) if (options.includeComments) { const comments = await commentsPromise; if (comments && comments.results && comments.results.length > 0) { enrichedPageData.comments = comments; } } return enrichedPageData; } catch (error) { if (error instanceof Error && error.message.includes('timed out')) { if (options.runInBackground) { console.error(`Timeout occurred while processing page ${pageId} at depth ${currentDepth}`); } return { id: pageId, error: "Operation timed out", partial_results: true, note: `Processing exceeded timeout limit (${options.timeoutMs}ms)` }; } if (options.runInBackground) { console.error(`Error in retrievePageRecursively for page ${pageId}:`, error); } return { id: pageId, error: error instanceof Error ? error.message : String(error), retrievalFailed: true }; } }
- Supporting helper for recursive block content retrieval, including child blocks, databases, and page info with parallel batch processing.// Recursively retrieve block content with improved parallelism private async retrieveBlocksRecursively(blockId: string, options: RecursiveExplorationOptions, currentDepth: number): Promise<any[]> { if (options.runInBackground) { console.log(`Recursive block exploration: ${blockId}, depth: ${currentDepth}/${options.maxDepth || 5}`); } if (currentDepth >= (options.maxDepth || 5)) { if (options.runInBackground) { console.log(`Maximum depth reached: ${currentDepth}/${options.maxDepth || 5}`); } return [{ note: "Maximum recursion depth reached" }]; } try { const operation = this.findOperation('API-get-block-children'); if (!operation) { throw new Error('API-get-block-children method not found.'); } const blocksResponse = await this.handleBlockChildrenParallel(operation, { block_id: blockId, page_size: 100 }, options); const blocksData = JSON.parse(blocksResponse.content[0].text); const blocks = blocksData.results || []; if (blocks.length === 0) { return []; } const batchSize = options.batchSize || 10; const enrichedBlocks: any[] = []; // Process blocks in batches for memory optimization and improved parallel execution for (let i = 0; i < blocks.length; i += batchSize) { const batch = blocks.slice(i, i + batchSize); // Process each batch in parallel const batchResults = await Promise.all( batch.map(async (block: any) => { this.blockCache.set(block.id, block); const enrichedBlock = { ...block }; // Collection of async tasks for this block const blockTasks: Promise<any>[] = []; // Process child blocks recursively if (block.has_children) { blockTasks.push( this.retrieveBlocksRecursively(block.id, options, currentDepth + 1) .then(childBlocks => { enrichedBlock.children = childBlocks; }) .catch(error => { console.error(`Error retrieving child blocks for ${block.id}:`, error); enrichedBlock.children_error = { message: String(error) }; return []; }) ); } // Process database blocks (if option enabled) if (options.includeDatabases && (block.type === 'child_database' || block.type === 'linked_database')) { const databaseId = block[block.type]?.database_id; if (databaseId) { blockTasks.push( this.retrieveDatabase(databaseId, options) .then(database => { enrichedBlock.database = database; }) .catch(error => { console.error(`Error retrieving database ${databaseId}:`, error); enrichedBlock.database_error = { message: String(error) }; }) ); } } // Process page blocks or linked pages - optimization if (block.type === 'child_page' && currentDepth < (options.maxDepth || 5) - 1) { const pageId = block.id; blockTasks.push( this.retrievePageBasicInfo(pageId, options) .then(pageInfo => { enrichedBlock.page_info = pageInfo; }) .catch(error => { console.error(`Error retrieving page info for ${pageId}:`, error); enrichedBlock.page_info_error = { message: String(error) }; }) ); } // Wait for all async tasks to complete if (blockTasks.length > 0) { await Promise.all(blockTasks); } return enrichedBlock; }) ); enrichedBlocks.push(...batchResults); } return enrichedBlocks; } catch (error) { console.error(`Error in retrieveBlocksRecursively for block ${blockId}:`, error); return [{ id: blockId, error: error instanceof Error ? error.message : String(error), retrievalFailed: true }]; } }