Skip to main content
Glama

API-get-one-pager

Retrieve complete Notion pages with all blocks, databases, and related content for comprehensive content extraction and analysis.

Instructions

Recursively retrieve a full Notion page with all its blocks, databases, and related content

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
page_idYesIdentifier for a Notion page
maxDepthNoMaximum recursion depth (default: 5)
includeDatabasesNoWhether to include linked databases (default: true)
includeCommentsNoWhether to include comments (default: true)
includePropertiesNoWhether to include detailed page properties (default: true)
maxParallelRequestsNoMaximum number of parallel requests (default: 15)
batchSizeNoBatch size for parallel processing (default: 10)
timeoutMsNoTimeout in milliseconds (default: 300000)
runInBackgroundNoProcess request in background without timeout (default: true)

Implementation Reference

  • Registration of the 'API-get-one-pager' tool in the ListToolsRequestSchema handler, defining its name, description, and input schema.
    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 point in CallToolRequestSchema handler that routes calls to 'API-get-one-pager' to the handleOnePagerRequest method.
    if (name === 'API-get-one-pager') {
      return await this.handleOnePagerRequest(params);
    }
  • Primary handler function for the 'API-get-one-pager' tool. Parses parameters, handles background/foreground modes, and orchestrates recursive page retrieval or returns processing status.
    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),
            },
          ],
        };
      }
    }
  • Core helper function that performs recursive retrieval of a Notion page, including blocks, databases, properties, and comments with caching, timeouts, and parallel processing.
    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
        };
      }
    }
  • Helper function for recursively retrieving and enriching block children, handling databases, child pages, with batched parallel processing.
    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
        }];
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions recursion and content inclusion but lacks details on performance implications, error handling, rate limits, authentication needs, or what the output looks like. For a complex tool with 9 parameters, this is a significant gap in transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose without unnecessary words. It directly communicates the tool's function, making it highly concise and well-structured for quick understanding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (9 parameters, no annotations, no output schema), the description is insufficient. It lacks information on output format, error conditions, performance trade-offs, and when to use alternatives, leaving the agent with incomplete context for effective tool invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all parameters. The description adds no additional parameter semantics beyond implying recursion and content scope, which is already covered by parameter names like 'maxDepth' and 'includeDatabases'. Baseline 3 is appropriate as the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('recursively retrieve') and the resource ('full Notion page with all its blocks, databases, and related content'), making the purpose specific and unambiguous. It distinguishes from siblings like 'API-retrieve-a-page' by emphasizing the comprehensive, recursive nature of the retrieval.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It does not mention any prerequisites, exclusions, or compare it to sibling tools like 'API-retrieve-a-page' for simpler needs or 'API-get-background-result' for background processing, leaving the agent without usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/Taewoong1378/notion-readonly-mcp-server'

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