create-onenote-page
Create a new OneNote page in your default notebook or specified section to organize notes and information.
Instructions
Create a new OneNote page in the default section of the default notebook. To create a page in a different section in the default notebook, you can use the sectionName query parameter. Example: ../onenote/pages?sectionName=My%20section The POST /onenote/pages operation is used only to create pages in the current user's default notebook. If you're targeting other notebooks, you can create pages in a specified section.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| body | Yes | ||
| includeHeaders | No | Include response headers (including ETag) in the response metadata | |
| excludeResponse | No | Exclude the full response body and only return success or failure indication |
Implementation Reference
- src/graph-tools.ts:86-369 (handler)The 'executeGraphTool' function acts as the central handler for executing all Microsoft Graph API tools, dynamically constructing the request based on tool definitions.
async function executeGraphTool( tool: (typeof api.endpoints)[0], config: EndpointConfig | undefined, graphClient: GraphClient, params: Record<string, unknown> ): Promise<CallToolResult> { logger.info(`Tool ${tool.alias} called with params: ${JSON.stringify(params)}`); try { const parameterDefinitions = tool.parameters || []; let path = tool.path; const queryParams: Record<string, string> = {}; const headers: Record<string, string> = {}; let body: unknown = null; for (const [paramName, paramValue] of Object.entries(params)) { // Skip control parameters - not part of the Microsoft Graph API if ( [ 'fetchAllPages', 'includeHeaders', 'excludeResponse', 'timezone', 'expandExtendedProperties', ].includes(paramName) ) { continue; } // Ok, so, MCP clients (such as claude code) doesn't support $ in parameter names, // and others might not support __, so we strip them in hack.ts and restore them here const odataParams = [ 'filter', 'select', 'expand', 'orderby', 'skip', 'top', 'count', 'search', 'format', ]; // Handle both "top" and "$top" formats - strip $ if present, then re-add it const normalizedParamName = paramName.startsWith('$') ? paramName.slice(1) : paramName; const isOdataParam = odataParams.includes(normalizedParamName.toLowerCase()); const fixedParamName = isOdataParam ? `$${normalizedParamName.toLowerCase()}` : paramName; // Look up param definition using normalized name (without $) for OData params const paramDef = parameterDefinitions.find( (p) => p.name === paramName || (isOdataParam && p.name === normalizedParamName) ); if (paramDef) { switch (paramDef.type) { case 'Path': { // Check if this parameter should skip URL encoding (for function-style API calls) const shouldSkipEncoding = config?.skipEncoding?.includes(paramName) ?? false; const encodedValue = shouldSkipEncoding ? (paramValue as string) : encodeURIComponent(paramValue as string); path = path .replace(`{${paramName}}`, encodedValue) .replace(`:${paramName}`, encodedValue); break; } case 'Query': if (paramValue !== '' && paramValue != null) { queryParams[fixedParamName] = `${paramValue}`; } break; case 'Body': if (paramDef.schema) { const parseResult = paramDef.schema.safeParse(paramValue); if (!parseResult.success) { const wrapped = { [paramName]: paramValue }; const wrappedResult = paramDef.schema.safeParse(wrapped); if (wrappedResult.success) { logger.info( `Auto-corrected parameter '${paramName}': AI passed nested field directly, wrapped it as {${paramName}: ...}` ); body = wrapped; } else { body = paramValue; } } else { body = paramValue; } } else { body = paramValue; } break; case 'Header': headers[fixedParamName] = `${paramValue}`; break; } } else if (paramName === 'body') { body = paramValue; logger.info(`Set body param: ${JSON.stringify(body)}`); } } // Handle timezone parameter for calendar endpoints if (config?.supportsTimezone && params.timezone) { headers['Prefer'] = `outlook.timezone="${params.timezone}"`; logger.info(`Setting timezone header: Prefer: outlook.timezone="${params.timezone}"`); } // Handle expandExtendedProperties parameter for calendar endpoints if (config?.supportsExpandExtendedProperties && params.expandExtendedProperties === true) { const expandValue = 'singleValueExtendedProperties'; if (queryParams['$expand']) { queryParams['$expand'] += `,${expandValue}`; } else { queryParams['$expand'] = expandValue; } logger.info(`Adding $expand=${expandValue} for extended properties`); } if (config?.contentType) { headers['Content-Type'] = config.contentType; logger.info(`Setting custom Content-Type: ${config.contentType}`); } if (Object.keys(queryParams).length > 0) { const queryString = Object.entries(queryParams) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .join('&'); path = `${path}${path.includes('?') ? '&' : '?'}${queryString}`; } const options: { method: string; headers: Record<string, string>; body?: string; rawResponse?: boolean; includeHeaders?: boolean; excludeResponse?: boolean; queryParams?: Record<string, string>; } = { method: tool.method.toUpperCase(), headers, }; if (options.method !== 'GET' && body) { if (config?.contentType === 'text/html') { if (typeof body === 'string') { options.body = body; } else if (typeof body === 'object' && 'content' in body) { options.body = (body as { content: string }).content; } else { options.body = String(body); } } else { options.body = typeof body === 'string' ? body : JSON.stringify(body); } } const isProbablyMediaContent = tool.errors?.some((error) => error.description === 'Retrieved media content') || path.endsWith('/content'); if (config?.returnDownloadUrl && path.endsWith('/content')) { path = path.replace(/\/content$/, ''); logger.info( `Auto-returning download URL for ${tool.alias} (returnDownloadUrl=true in endpoints.json)` ); } else if (isProbablyMediaContent) { options.rawResponse = true; } // Set includeHeaders if requested if (params.includeHeaders === true) { options.includeHeaders = true; } // Set excludeResponse if requested if (params.excludeResponse === true) { options.excludeResponse = true; } logger.info(`Making graph request to ${path} with options: ${JSON.stringify(options)}`); let response = await graphClient.graphRequest(path, options); const fetchAllPages = params.fetchAllPages === true; if (fetchAllPages && response?.content?.[0]?.text) { try { let combinedResponse = JSON.parse(response.content[0].text); let allItems = combinedResponse.value || []; let nextLink = combinedResponse['@odata.nextLink']; let pageCount = 1; while (nextLink && pageCount < 100) { logger.info(`Fetching page ${pageCount + 1} from: ${nextLink}`); const url = new URL(nextLink); const nextPath = url.pathname.replace('/v1.0', ''); const nextOptions = { ...options }; const nextQueryParams: Record<string, string> = {}; for (const [key, value] of url.searchParams.entries()) { nextQueryParams[key] = value; } nextOptions.queryParams = nextQueryParams; const nextResponse = await graphClient.graphRequest(nextPath, nextOptions); if (nextResponse?.content?.[0]?.text) { const nextJsonResponse = JSON.parse(nextResponse.content[0].text); if (nextJsonResponse.value && Array.isArray(nextJsonResponse.value)) { allItems = allItems.concat(nextJsonResponse.value); } nextLink = nextJsonResponse['@odata.nextLink']; pageCount++; } else { break; } } if (pageCount >= 100) { logger.warn(`Reached maximum page limit (100) for pagination`); } combinedResponse.value = allItems; if (combinedResponse['@odata.count']) { combinedResponse['@odata.count'] = allItems.length; } delete combinedResponse['@odata.nextLink']; response.content[0].text = JSON.stringify(combinedResponse); logger.info( `Pagination complete: collected ${allItems.length} items across ${pageCount} pages` ); } catch (e) { logger.error(`Error during pagination: ${e}`); } } if (response?.content?.[0]?.text) { const responseText = response.content[0].text; logger.info(`Response size: ${responseText.length} characters`); try { const jsonResponse = JSON.parse(responseText); if (jsonResponse.value && Array.isArray(jsonResponse.value)) { logger.info(`Response contains ${jsonResponse.value.length} items`); } if (jsonResponse['@odata.nextLink']) { logger.info(`Response has pagination nextLink: ${jsonResponse['@odata.nextLink']}`); } } catch { // Non-JSON response } } // Convert McpResponse to CallToolResult with the correct structure const content: ContentItem[] = response.content.map((item) => ({ type: 'text' as const, text: item.text, })); return { content, _meta: response._meta, isError: response.isError, }; } catch (error) { logger.error(`Error in tool ${tool.alias}: ${(error as Error).message}`); return { content: [ { type: 'text', text: JSON.stringify({ error: `Error in tool ${tool.alias}: ${(error as Error).message}`, }), }, ], isError: true, }; } } - src/graph-tools.ts:371-489 (registration)The 'registerGraphTools' function registers all tools defined in the API metadata to the MCP server, binding them to the 'executeGraphTool' handler.
export function registerGraphTools( server: McpServer, graphClient: GraphClient, readOnly: boolean = false, enabledToolsPattern?: string, orgMode: boolean = false ): number { let enabledToolsRegex: RegExp | undefined; if (enabledToolsPattern) { try { enabledToolsRegex = new RegExp(enabledToolsPattern, 'i'); logger.info(`Tool filtering enabled with pattern: ${enabledToolsPattern}`); } catch { logger.error(`Invalid tool filter regex pattern: ${enabledToolsPattern}. Ignoring filter.`); } } let registeredCount = 0; let skippedCount = 0; let failedCount = 0; for (const tool of api.endpoints) { const endpointConfig = endpointsData.find((e) => e.toolName === tool.alias); if (!orgMode && endpointConfig && !endpointConfig.scopes && endpointConfig.workScopes) { logger.info(`Skipping work account tool ${tool.alias} - not in org mode`); skippedCount++; continue; } if (readOnly && tool.method.toUpperCase() !== 'GET') { logger.info(`Skipping write operation ${tool.alias} in read-only mode`); skippedCount++; continue; } if (enabledToolsRegex && !enabledToolsRegex.test(tool.alias)) { logger.info(`Skipping tool ${tool.alias} - doesn't match filter pattern`); skippedCount++; continue; } const paramSchema: Record<string, z.ZodTypeAny> = {}; if (tool.parameters && tool.parameters.length > 0) { for (const param of tool.parameters) { paramSchema[param.name] = param.schema || z.any(); } } if (tool.method.toUpperCase() === 'GET' && tool.path.includes('/')) { paramSchema['fetchAllPages'] = z .boolean() .describe('Automatically fetch all pages of results') .optional(); } // Add includeHeaders parameter for all tools to capture ETags and other headers paramSchema['includeHeaders'] = z .boolean() .describe('Include response headers (including ETag) in the response metadata') .optional(); // Add excludeResponse parameter to only return success/failure indication paramSchema['excludeResponse'] = z .boolean() .describe('Exclude the full response body and only return success or failure indication') .optional(); // Add timezone parameter for calendar endpoints that support it if (endpointConfig?.supportsTimezone) { paramSchema['timezone'] = z .string() .describe( 'IANA timezone name (e.g., "America/New_York", "Europe/London", "Asia/Tokyo") for calendar event times. If not specified, times are returned in UTC.' ) .optional(); } // Add expandExtendedProperties parameter for calendar endpoints that support it if (endpointConfig?.supportsExpandExtendedProperties) { paramSchema['expandExtendedProperties'] = z .boolean() .describe( 'When true, expands singleValueExtendedProperties on each event. Use this to retrieve custom extended properties (e.g., sync metadata) stored on calendar events.' ) .optional(); } // Build the tool description, optionally appending LLM tips let toolDescription = tool.description || `Execute ${tool.method.toUpperCase()} request to ${tool.path}`; if (endpointConfig?.llmTip) { toolDescription += `\n\nš” TIP: ${endpointConfig.llmTip}`; } try { server.tool( tool.alias, toolDescription, paramSchema, { title: tool.alias, readOnlyHint: tool.method.toUpperCase() === 'GET', destructiveHint: ['POST', 'PATCH', 'DELETE'].includes(tool.method.toUpperCase()), openWorldHint: true, // All tools call Microsoft Graph API }, async (params) => executeGraphTool(tool, endpointConfig, graphClient, params) ); registeredCount++; } catch (error) { logger.error(`Failed to register tool ${tool.alias}: ${(error as Error).message}`); failedCount++; } } logger.info( `Tool registration complete: ${registeredCount} registered, ${skippedCount} skipped, ${failedCount} failed` ); return registeredCount; }