UNPKG

@davidmoore-io/ms-365-mcp-server

Version:

Microsoft 365 MCP Server - Fork with pre-authenticated token support

102 lines (101 loc) 4.69 kB
import logger from './logger.js'; import { api } from './generated/client.js'; import { z } from 'zod'; export function registerGraphTools(server, graphClient, readOnly = false) { for (const tool of api.endpoints) { if (readOnly && tool.method.toUpperCase() !== 'GET') { logger.info(`Skipping write operation ${tool.alias} in read-only mode`); continue; } const paramSchema = {}; if (tool.parameters && tool.parameters.length > 0) { for (const param of tool.parameters) { // Use z.any() as a fallback schema if the parameter schema is not specified paramSchema[param.name] = param.schema || z.any(); } } server.tool(tool.alias, tool.description ?? '', paramSchema, { title: tool.alias, readOnlyHint: tool.method.toUpperCase() === 'GET', }, async (params, extra) => { logger.info(`Tool ${tool.alias} called with params: ${JSON.stringify(params)}`); try { logger.info(`params: ${JSON.stringify(params)}`); const parameterDefinitions = tool.parameters || []; let path = tool.path; const queryParams = {}; const headers = {}; let body = null; for (let [paramName, paramValue] of Object.entries(params)) { const fixedParamName = paramName.replace(/__/g, '$'); const paramDef = parameterDefinitions.find((p) => p.name === paramName); if (paramDef) { switch (paramDef.type) { case 'Path': path = path .replace(`{${paramName}}`, encodeURIComponent(paramValue)) .replace(`:${paramName}`, encodeURIComponent(paramValue)); break; case 'Query': queryParams[fixedParamName] = `${paramValue}`; break; case 'Body': body = paramValue; break; case 'Header': headers[fixedParamName] = `${paramValue}`; break; } } else if (paramName === 'body') { body = paramValue; logger.info(`Set legacy body param: ${JSON.stringify(body)}`); } } 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: tool.method.toUpperCase(), headers, }; if (options.method !== 'GET' && body) { options.body = JSON.stringify(body); } logger.info(`Making graph request to ${path} with options: ${JSON.stringify(options)}`); const response = await graphClient.graphRequest(path, options); // Convert McpResponse to CallToolResult with the correct structure const content = response.content.map((item) => { // GraphClient only returns text content items, so create proper TextContent items const textContent = { type: 'text', text: item.text, }; return textContent; }); const result = { content, _meta: response._meta, isError: response.isError, }; return result; } catch (error) { logger.error(`Error in tool ${tool.alias}: ${error.message}`); const errorContent = { type: 'text', text: JSON.stringify({ error: `Error in tool ${tool.alias}: ${error.message}`, }), }; return { content: [errorContent], isError: true, }; } }); } }