Skip to main content
Glama

create-onenote-page

Destructive

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;
    }
Behavior3/5

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

Mentions POST operation and default notebook constraint. Annotations indicate destructive and open-world behavior; the description adds context about the default notebook scope but omits details about auth requirements, rate limits, or error behaviors.

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

Conciseness3/5

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

Four sentences with reasonable information density, though the URL example ('../onenote/pages?sectionName...') is confusing in a tool context since MCP tools don't use URL query parameters directly.

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 complex nested body object with many read-only fields and no output schema, the description provides insufficient guidance on required fields or expected response structure. It does not explain how to populate the required 'body' parameter.

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

Parameters2/5

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

Misleadingly references 'sectionName query parameter' which does not exist in the input schema (only body, includeHeaders, excludeResponse are present). With 67% schema coverage, the description fails to compensate for the complex nested body structure and instead adds confusing API-specific details that don't map to the tool interface.

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

Purpose4/5

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

States specific action (Create) and resource (OneNote page) with clear scope constraints (default section of default notebook). Mentions limitation of targeting only default notebooks, implicitly distinguishing from sibling `create-onenote-section-page`.

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

Usage Guidelines3/5

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

Provides guidance on using sectionName parameter for different sections and notes the default notebook limitation. However, fails to explicitly name the sibling tool (`create-onenote-section-page`) for targeting other notebooks, leaving ambiguity about the alternative.

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

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