API-retrieve-a-page
Use this tool to retrieve specific Notion pages by their ID and filter page properties to limit responses, enabling precise data extraction from Notion workspaces via the MCP server.
Instructions
Notion | Retrieve a page
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filter_properties | No | A list of page property value IDs associated with the page. Use this param to limit the response to a specific page property value or values. To retrieve multiple properties, specify each page property ID. For example: `?filter_properties=iAk8&filter_properties=b7dh`. | |
| page_id | Yes | Identifier for a Notion page |
Implementation Reference
- The main handler for executing the 'API-retrieve-a-page' tool (and all generated tools). It resolves the tool name to an OpenAPI operation and calls the HTTP client to execute it.this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: params } = request.params // Find the operation in OpenAPI spec const operation = this.findOperation(name) if (!operation) { throw new Error(`Method ${name} not found`) } try { // Execute the operation const response = await this.httpClient.executeOperation(operation, params) // Convert response to MCP format return { content: [ { type: 'text', // currently this is the only type that seems to be used by mcp server text: JSON.stringify(response.data), // TODO: pass through the http status code text? }, ], } } catch (error) { console.error('Error in tool call', error) if (error instanceof HttpClientError) { console.error('HttpClientError encountered, returning structured error', error) const data = error.data?.response?.data ?? error.data ?? {} return { content: [ { type: 'text', text: JSON.stringify({ status: 'error', // TODO: get this from http status code? ...(typeof data === 'object' ? data : { data: data }), }), }, ], } } throw error } })
- Helper function that executes the actual HTTP request for the OpenAPI operation corresponding to the tool, using openapi-client-axios.async executeOperation<T = any>( operation: OpenAPIV3.OperationObject & { method: string; path: string }, params: Record<string, any> = {}, ): Promise<HttpClientResponse<T>> { const api = await this.api const operationId = operation.operationId if (!operationId) { throw new Error('Operation ID is required') } // Handle file uploads if present const formData = await this.prepareFileUpload(operation, params) // Separate parameters based on their location const urlParameters: Record<string, any> = {} const bodyParams: Record<string, any> = formData || { ...params } // Extract path and query parameters based on operation definition if (operation.parameters) { for (const param of operation.parameters) { if ('name' in param && param.name && param.in) { if (param.in === 'path' || param.in === 'query') { if (params[param.name] !== undefined) { urlParameters[param.name] = params[param.name] if (!formData) { delete bodyParams[param.name] } } } } } } // Add all parameters as url parameters if there is no requestBody defined if (!operation.requestBody && !formData) { for (const key in bodyParams) { if (bodyParams[key] !== undefined) { urlParameters[key] = bodyParams[key] delete bodyParams[key] } } } const operationFn = (api as any)[operationId] if (!operationFn) { throw new Error(`Operation ${operationId} not found`) } try { // If we have form data, we need to set the correct headers const hasBody = Object.keys(bodyParams).length > 0 const headers = formData ? formData.getHeaders() : { ...(hasBody ? { 'Content-Type': 'application/json' } : { 'Content-Type': null }) } const requestConfig = { headers: { ...headers, }, } // first argument is url parameters, second is body parameters const response = await operationFn(urlParameters, hasBody ? bodyParams : undefined, requestConfig) // Convert axios headers to Headers object const responseHeaders = new Headers() Object.entries(response.headers).forEach(([key, value]) => { if (value) responseHeaders.append(key, value.toString()) }) return { data: response.data, status: response.status, headers: responseHeaders, } } catch (error: any) { if (error.response) { console.error('Error in http client', error) const headers = new Headers() Object.entries(error.response.headers).forEach(([key, value]) => { if (value) headers.append(key, value.toString()) }) throw new HttpClientError(error.response.statusText || 'Request failed', error.response.status, error.response.data, headers) } throw error } }
- Generates the input schema for each MCP tool (including 'API-retrieve-a-page') from the OpenAPI operation's parameters and requestBody.private convertOperationToMCPMethod(operation: OpenAPIV3.OperationObject, method: string, path: string): NewToolMethod | null { if (!operation.operationId) { console.warn(`Operation without operationId at ${method} ${path}`) return null } const methodName = operation.operationId const inputSchema: IJsonSchema & { type: 'object' } = { $defs: this.convertComponentsToJsonSchema(), type: 'object', properties: {}, required: [], } // Handle parameters (path, query, header, cookie) if (operation.parameters) { for (const param of operation.parameters) { const paramObj = this.resolveParameter(param) if (paramObj && paramObj.schema) { const schema = this.convertOpenApiSchemaToJsonSchema(paramObj.schema, new Set(), false) // Merge parameter-level description if available if (paramObj.description) { schema.description = paramObj.description } inputSchema.properties![paramObj.name] = schema if (paramObj.required) { inputSchema.required!.push(paramObj.name) } } } } // Handle requestBody if (operation.requestBody) { const bodyObj = this.resolveRequestBody(operation.requestBody) if (bodyObj?.content) { // Handle multipart/form-data for file uploads // We convert the multipart/form-data schema to a JSON schema and we require // that the user passes in a string for each file that points to the local file if (bodyObj.content['multipart/form-data']?.schema) { const formSchema = this.convertOpenApiSchemaToJsonSchema(bodyObj.content['multipart/form-data'].schema, new Set(), false) if (formSchema.type === 'object' && formSchema.properties) { for (const [name, propSchema] of Object.entries(formSchema.properties)) { inputSchema.properties![name] = propSchema } if (formSchema.required) { inputSchema.required!.push(...formSchema.required!) } } } // Handle application/json else if (bodyObj.content['application/json']?.schema) { const bodySchema = this.convertOpenApiSchemaToJsonSchema(bodyObj.content['application/json'].schema, new Set(), false) // Merge body schema into the inputSchema's properties if (bodySchema.type === 'object' && bodySchema.properties) { for (const [name, propSchema] of Object.entries(bodySchema.properties)) { inputSchema.properties![name] = propSchema } if (bodySchema.required) { inputSchema.required!.push(...bodySchema.required!) } } else { // If the request body is not an object, just put it under "body" inputSchema.properties!['body'] = bodySchema inputSchema.required!.push('body') } } } } // Build description including error responses let description = operation.summary || operation.description || '' if (operation.responses) { const errorResponses = Object.entries(operation.responses) .filter(([code]) => code.startsWith('4') || code.startsWith('5')) .map(([code, response]) => { const responseObj = this.resolveResponse(response) let errorDesc = responseObj?.description || '' return `${code}: ${errorDesc}` }) if (errorResponses.length > 0) { description += '\nError Responses:\n' + errorResponses.join('\n') } } // Extract return type (response schema) const returnSchema = this.extractResponseType(operation.responses) // Generate Zod schema from input schema try { // const zodSchemaStr = jsonSchemaToZod(inputSchema, { module: "cjs" }) // console.log(zodSchemaStr) // // Execute the function with the zod instance // const zodSchema = eval(zodSchemaStr) as z.ZodType return { name: methodName, description, inputSchema, ...(returnSchema ? { returnSchema } : {}), } } catch (error) { console.warn(`Failed to generate Zod schema for ${methodName}:`, error) // Fallback to a basic object schema return { name: methodName, description, inputSchema, ...(returnSchema ? { returnSchema } : {}), } } }
- src/openapi-mcp-server/mcp/proxy.ts:58-75 (registration)Registers the listTools handler which dynamically lists all tools, including 'API-retrieve-a-page', generated from the OpenAPI spec.this.server.setRequestHandler(ListToolsRequestSchema, async () => { const tools: Tool[] = [] // Add methods as separate tools to match the MCP format Object.entries(this.tools).forEach(([toolName, def]) => { def.methods.forEach(method => { const toolNameWithMethod = `${toolName}-${method.name}`; const truncatedToolName = this.truncateToolName(toolNameWithMethod); tools.push({ name: truncatedToolName, description: method.description, inputSchema: method.inputSchema as Tool['inputSchema'], }) }) }) return { tools } })
- In the MCPProxy constructor, converts the OpenAPI spec to MCP tools and lookup map, populating the 'API-retrieve-a-page' entry in openApiLookup.const converter = new OpenAPIToMCPConverter(openApiSpec) const { tools, openApiLookup } = converter.convertToMCPTools() this.tools = tools this.openApiLookup = openApiLookup