Skip to main content
Glama

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
NameRequiredDescriptionDefault
bodyYes
includeHeadersNoInclude response headers (including ETag) in the response metadata
excludeResponseNoExclude the full response body and only return success or failure indication

Implementation Reference

  • 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,
        };
      }
    }
  • 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;
    }
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/alfredo-ia/ms-365-mcp-server'

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