@davidmoore-io/ms-365-mcp-server
Version:
Microsoft 365 MCP Server - Fork with pre-authenticated token support
102 lines (101 loc) • 4.69 kB
JavaScript
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,
};
}
});
}
}